web/Spring

Spring framework에서 html을 pdf만들어 다운로드 하기

반응형

업무적으로 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(), 36365436);
    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

반응형