업무적으로 html 코드를 이용하여 pdf 파일로 내보내기 위해서 자료수집을 많이했다.
하지만 결론부터 이야기하면 원하는대로 다 되지는 않았다.
밑에 이야기 하겠지만 html을 pdf로 만들어주는 라이브러리가 정확하게 html모든 태그를 파싱하지 못할뿐만 아니라, css적용도 정상적으로 되지 않았다.
그래도 다시해본 결과!!!!! ( 이 글 쓰고나서 더 조사해본 결과.. 포기하지 마시길 )
=> html2pdf를 사용하면 거의 대부분의 css를 적용할 수있다.
1. pom.xml 설정
itextpdf -> pdf를 생성하기 위해 필요한 라이브러리
xmlworker -> xml 파싱을 위해 필요한 라이브러리
html2pdf -> itext의 7버전을 사용하기 위해 필요한 라이브러리
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.13</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/html2pdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>html2pdf</artifactId> <version>2.0.2</version> </dependency> | cs |
2. view 관련 설정
BeanNameViewResolver 설정은 뷰 이름과 동일한 이름을 가진 빈을 뷰 클래스로 사용한다.
ex) 만약 pdfview라는 view를 컨트롤러에서 호출 할 시 해당요청에 대한 view는 pdfview 클래스에 처리
1 2 3 | <beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <beans:property name="order" value="0" /> </beans:bean> | cs |
3. AbstractView 클래스 설정
기존의 Spring에서 제공하는 AbstractPdfView 클래스를 이용하여 pdf view를 사용할 수 있으나, 해당 클래스는 com.itext라이브러리가 아닌 com.lowagie만을 사용할 수 있다.
두 개의 라이브러리는 서로 같지만 com.lowagie 라이브러리는 com.itext의 2.1.7버전과 동일하고 더이상 지원이 멈추어서 새로 제공되는 기능을 사용할 수 없다. 그렇기 때문에 이 문제를 해결하기 위해서 AbstractView를 상속하여 com.itext 라이브러리를 사용할 수 있는 클래스를 재정의 해야한다.
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 | package com.wedul.pdf; import java.io.ByteArrayOutputStream; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.view.AbstractView; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.PageSize; import com.itextpdf.text.pdf.PdfWriter; public abstract class AbstractITextPdfView extends AbstractView { public AbstractITextPdfView() { setContentType("application/pdf"); } @Override protected boolean generatesDownloadContent() { return true; } @Override protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // IE workaround: write into byte array first. ByteArrayOutputStream baos = createTemporaryOutputStream(); // Apply preferences and build metadata. Document document = new Document(PageSize.A4.rotate(), 36, 36, 54, 36); PdfWriter writer = PdfWriter.getInstance(document, baos); prepareWriter(model, writer, request); buildPdfMetadata(model, document, request); // Build PDF document. document.open(); buildPdfDocument(model, document, writer, request, response); document.close(); // Flush to HTTP response. response.setHeader("Content-Disposition", "attachment"); // make browser to ask for download/display writeToResponse(response, baos); } protected void prepareWriter(Map<String, Object> model, PdfWriter writer, HttpServletRequest request) throws DocumentException { writer.setViewerPreferences(getViewerPreferences()); } protected int getViewerPreferences() { return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage; } protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) { } protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception; } | cs |
그리고 기본적으로 폰트가 한글을 지원하지 않기때문에 한글지원을 위해서 폰트를 제공해 주어야한다. 이를 위해서 사용하는 fontprovider 클래스를 구현해야한다.
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 | package com.wedul.pdf; import java.io.IOException; import com.itextpdf.text.BaseColor; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Font; import com.itextpdf.text.FontFactoryImp; import com.itextpdf.text.pdf.BaseFont; class DefaultFontProvider extends FontFactoryImp { private String _default; public DefaultFontProvider(String def) { _default = def; } // I believe this is the correct override, but there are quite a few others. public Font getFont(String fontname,String encoding, boolean embedded, float size,int style, BaseColor color) { try { //한글 깨짐을 방지 하기위한 폰트 세팅 return new Font(BaseFont.createFont(_default, BaseFont.IDENTITY_H, BaseFont.EMBEDDED), 9, style, BaseColor.BLACK); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } } | cs |
3. Controller
1 2 3 4 5 6 | @RequestMapping(value = "/download/pdf", method = RequestMethod.GET) public ModelAndView downloadPdf() { ModelAndView modelAndView = new ModelAndView("pdfView", "fileName", "test.pdf"); return modelAndView; } | cs |
4. PdfView 클래스
실질적인 pdf 파일을 만드는 클래스이다. pdf를 만드는 라이브러리는 버전 별로 사용하는 방법이 다르다.
1.) 2.x 버전
HTMLWorker를 사용하여 만들수 있다. 하지만 아주 기초적인 태그에 대해서 지원해주고 css지원이 적으나 심플하게 pdf를 만들 수 있기 때문에 가장 많이 사용 되는 버전이다.
2.) 5.x 버전
XMLWorker와 XMLParser를 통해서 pdf를 만들수있다. 하지만 여기서 문제가 발생했는데, 단순하게 XMLParser 클래스에서 parse 메소드를 통해서 pdf를 만들수있지만 div 태그가 포함될경우 만들어지지 않는다. 또 XMLWorkerHelper 클래스를 통해서 각각 Element를 사용할 경우 css가 적용이 되지 않는다. (삽질을 하게 만들었다 아주 맘에 안들었다...)
3.) 7.x
HtmlConverter 클래스를 통해서 변경할 수 있지만 이것을 spring에서 사용하기에는 좀 부적절 했다.
-> Spring에서 ModelAndView를 사용해서 pdf를 만들지 않고 라이브러리에서 제공하는 방식으로 하면 가장 좋게 만들어진다.
모든 버전에 대한 코드를 적어보았다..
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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | import java.io.FileInputStream; import java.io.StringReader; import java.nio.charset.Charset; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import com.itextpdf.text.Document; import com.itextpdf.text.Element; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.tool.xml.ElementList; import com.itextpdf.tool.xml.XMLWorker; import com.itextpdf.tool.xml.XMLWorkerFontProvider; import com.itextpdf.tool.xml.XMLWorkerHelper; import com.itextpdf.tool.xml.css.CssFile; import com.itextpdf.tool.xml.css.StyleAttrCSSResolver; import com.itextpdf.tool.xml.html.CssAppliers; import com.itextpdf.tool.xml.html.CssAppliersImpl; import com.itextpdf.tool.xml.html.Tags; import com.itextpdf.tool.xml.parser.XMLParser; import com.itextpdf.tool.xml.pipeline.css.CSSResolver; import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline; import com.itextpdf.tool.xml.pipeline.end.ElementHandlerPipeline; import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline; import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext; @Component public class PDFView extends AbstractITextPdfView { @SuppressWarnings({ "static-access", "deprecation", "unchecked" }) protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { // version 7 // PdfWriter writer2 = PdfWriter.getInstance(document, response.getOutputStream()); // String fileName = String.valueOf(model.get("fileName")); // // // 파일 다운로드 설정 // response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";"); // response.setHeader("Content-Transfer-Encoding", "binary"); // response.setContentType("application/pdf"); // // document.open(); // XMLWorkerHelper helper = XMLWorkerHelper.getInstance(); // // // 폰트 설정에서 별칭으로 줬던 "MalgunGothic"을 html 안에 폰트로 지정한다. // String htmlStr = "<html><head><body style='font-family: MalgunGothic;'>" // + "<p>PDF 안에 들어갈 내용입니다.</p>" // + "<div><h3>한글, English, 漢字.</h3></div>" // + "</body></head></html>"; // // HtmlConverter.convertToPdf(htmlStr, response.getOutputStream(), null); // version 5 // PdfWriter 생성 PdfWriter.getInstance(document, response.getOutputStream()); String fileName = String.valueOf(model.get("fileName")); // 파일 다운로드 설정 response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";"); response.setHeader("Content-Transfer-Encoding", "binary"); response.setContentType("application/pdf"); // Document 오픈 document.open(); // CSS CSSResolver cssResolver = new StyleAttrCSSResolver(); CssFile cssFile = XMLWorkerHelper.getInstance().getCSS(new FileInputStream(PDFView.class.getClassLoader().getResource("pdf.css").getPath())); cssResolver.addCss(cssFile); // HTML, 폰트 설정 XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS); fontProvider.register(PDFView.class.getClassLoader().getResource("malgun.ttf").getPath(), "MalgunGothic"); // MalgunGothic은 alias, CssAppliers cssAppliers = new CssAppliersImpl(fontProvider); HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers); htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory()); // Pipelines ElementList elements = new ElementList(); ElementHandlerPipeline end = new ElementHandlerPipeline(elements, null); HtmlPipeline html = new HtmlPipeline(htmlContext, end); CssResolverPipeline css = new CssResolverPipeline(cssResolver, html); String htmlStr0 = "<html><head><body style='font-family: MalgunGothic;'>" + "<p>PDF 안에sdf 들어갈 내용입니다.</p>" + "<div style='text-align:center; font-size:30px;'; ><h3>한글sdf, English, 漢字.</h3></div>" + "</body></head></html>"; XMLWorker worker = new XMLWorker(css, true); XMLParser xmlParser = new XMLParser(worker, Charset.forName("UTF-8")); for (int i =0 ; i <= 1 ; i++) { // 폰트 설정에서 별칭으로 줬던 "MalgunGothic"을 html 안에 폰트로 지정한다. StringReader strReader; strReader = new StringReader(htmlStr0); xmlParser.parse(strReader); PdfPTable table = new PdfPTable(1); PdfPCell cell = new PdfPCell(); for (Element element : elements) { cell.addElement(element); } table.addCell(cell); document.add(table); document.newPage(); } document.close(); writer.close(); // version 2 // String fileName = String.valueOf(model.get("fileName")); // response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";"); // response.setHeader("Content-Transfer-Encoding", "binary"); // response.setContentType("application/pdf"); // // StyleSheet css = new StyleSheet(); // css.loadTagStyle(HtmlTags.H1, "color", "red"); // // HTMLWorker htmlWorker = new HTMLWorker(document); // HashMap<String, Object> interfaceProps = new HashMap<String, Object>(); // // DefaultFontProvider dfp = new DefaultFontProvider(PDFView.class.getClassLoader().getResource("malgun.ttf").getPath()); // interfaceProps.put(HTMLWorker.FONT_PROVIDER, dfp); // // StringReader reader = null; // PdfWriter.getInstance(document, response.getOutputStream()); // document.open(); // // reader = new StringReader( // new // StringBuffer("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">") // .append("<html xmlns=\"http://www.w3.org/1999/xhtml\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>").append("<h1>").append("test"). // append("</h1>") // .append("테스트 입니다").append("</body></html>").toString()); // // List<Element> objects = htmlWorker.parseToList(reader, css, interfaceProps); // for (int k = 0; k < objects.size(); ++k) { // document.add((Element) objects.get(k)); // } // // document.close(); } } | cs |
삽질의 결론은 스프링에서 Pdf ModelAndView를 사용할 때는 2.x 버전의 HTMLWorker를 쓰기로 하였고 단순 pdf파일을 만들때는 7.x 버전을 사용하기로 하였다.
몇가지 삽질 하면서 찾은 고생하신 분들의 출처 사이트도 적어본다.
출처
버전 2
http://zero-gravity.tistory.com/251
버전 5
http://kooremo.tistory.com/m/entry/itext이용해서-pdf파일-만드는-샘플-소스
XMLParser 사용시 div가 포함된 html이 생성되지 않는다고 고민하는 사람의 주소
http://itext.2136553.n4.nabble.com/Problems-with-the-font-tags-and-div-tags-in-xml-worker-td4657646.html
소스 풀버전은 내 github 페이지에서 확인할 수 있다.
https://github.com/weduls/pdfdown_example
'web > Spring' 카테고리의 다른 글
mac환경에서 spring boot에 lombok 설치하기 (0) | 2018.06.03 |
---|---|
Spring, spring-boot의 mvc 다양한 설정 설명 (0) | 2018.06.03 |
Spring boot에서 Spring security를 사용하여 로그인 하기 (12) | 2018.05.27 |
spring boot에 https 접속 적용하기 (0) | 2018.05.27 |
Spring에서 url 요청하는 RestTemplate 설명 (0) | 2018.05.27 |