인생 디벨로퍼

[Spring boot] HTML 파일 -> PDF 전환 본문

JAVA Spring

[Spring boot] HTML 파일 -> PDF 전환

뫄뫙뫄 2024. 11. 21. 10:49
728x90

코드 순서가 아닌, 실행순서 위주로 정리했습니다.

1. 라이브러리 설정

// Thymeleaf 사용, HTML 파일 기반 동적 콘텐츠 생성 가능
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

//  XHTML 및 CSS를 기반으로 PDF를 생성할 수 있는 라이브러리. 내부적으로 iText 라이브러리를 사용
implementation 'org.xhtmlrenderer:flying-saucer-pdf:9.9.5'

2. 파일 저장 경로 설정

    static String userDir() {
        String userDir = System.getProperty("user.dir").toString();

        logger.info("userDir"+userDir);

        if (userDir.contains("root")) { //서버에 올라갔을때
            userDir = "/root";
        }else{
            
            Path paths = Paths.get(userDir);
            userDir = paths.getParent().toString();
        }
        return userDir;
    }

프로젝트 밖에 저장 되도록 기본 루트를 정합니다. 

상황에 따라 다른 디렉토리를 생성해야할 수 있을거 같아, 디렉토리 생성은 하단 메소드에서 실행했습니다.

3. HTML 을 String 으로 변환

    private static String parseHtmlFileToString(String templateCode, Map<String, Object> map){ 
        // 타임리프 템플릿을 로딩 리졸버 (경로 : src/main/resources/)
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        resolver.setPrefix("templates/");
        resolver.setSuffix(".html"); // 템플릿 파일 확장자 지정
        resolver.setCharacterEncoding("UTF-8"); // 템플릿 파일 문자열 인코딩
        resolver.setForceTemplateMode(true); //파일 확장자나 내용에 관계없이 강제로 설정된 템플릿 모드를 사용
        resolver.setTemplateMode(TemplateMode.HTML); //템플릿을 HTML 모드로 렌더링

        //템플릿엔진 생성, 위의 ClassLoaderTemplateResolver 연결
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(resolver);

        // 템플릿 렌더링, 문자열 반환
        Context context = new Context();
        context.setVariables(map);

        return engine.process(templateCode, context);
    }
  • String templateCode: 사용할 템플릿 HTML 파일 명, (당연히 /templates 경로 안에 있어야함)
  • Map<String, Object> map: 사용할 변수. 다음 메소드로 넘겨줄꺼다

지정한 html 파일과 context 를 읽어 String 으로 반환 해줍니다.

4. 변수 설정, 폰트 설정

    private static ITextRenderer getRenderer(String html, Map<String, Object> pMap) throws IOException, DocumentException{

        // 폰트 설정. 해줘야 한글이 나옴
        ClassPathResource fontResource = new ClassPathResource("static/font/SpoqaHanSansNeo-Regular.ttf");
        if (fontResource.exists()) {
            System.out.println("Font file exists at: " + fontResource.getPath());
        } else {
            System.out.println("Font file does not exist.");
        }

		// 변수 데이터 넣어주기
        for (String key : pMap.keySet()) {
            html = html.replaceAll("@" + key + "@", pMap.get(key).toString());
        }
        
        // 이미지 변환 메소드로 던진다
        B64ImgReplacedElementFactory b64ImgReplacedElementFactory = new B64ImgReplacedElementFactory();
        ITextRenderer renderer = new ITextRenderer();

        renderer.getSharedContext().setReplacedElementFactory(b64ImgReplacedElementFactory);

        renderer.setDocumentFromString(html);
//        System.out.println(renderer.getFontResolver().toString());
        renderer.getFontResolver()
                .addFont(new ClassPathResource("static/font/NotoSansKR-Regular.ttf").getURL().toString(),
                        BaseFont.IDENTITY_H,
                        BaseFont.EMBEDDED);

        renderer.layout();

        return renderer;
    }
  • String html: string 으로 변환시킨 html 템플릿
  • Map<String, Object> pMap: 변수에 설정해줄 데이터

"@" + key + "@" 로 변수를 넣어줌

5. 이미지 변환

public class B64ImgReplacedElementFactory implements ReplacedElementFactory {
    public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) {
        Element e = box.getElement();
        if (e == null) {
            return null;
        }
        String nodeName = e.getNodeName();
        // <img> 태그를 찾아서, src 주소에 있는 이미지 파일 찾아옴
        if (nodeName.equals("img")) {
            String attribute = e.getAttribute("src");
            FSImage fsImage;
            try {
                fsImage = buildImage(attribute, uac);
            } catch (BadElementException e1) {
                fsImage = null;
            } catch (IOException e1) {
                fsImage = null;
            }
            if (fsImage != null) {
                if (cssWidth != -1 || cssHeight != -1) {
                    fsImage.scale(cssWidth, cssHeight);
                }
                return new ITextImageElement(fsImage);
            }
        }
        return null;
    }

    protected FSImage buildImage(String srcAttr, UserAgentCallback uac)
            throws IOException, BadElementException {
        FSImage fsImage;
        if (srcAttr.startsWith("data:image/")) {
            String b64encoded =
                    srcAttr.substring(
                            srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length());

            byte[] decodedBytes = Base64.getDecoder().decode(b64encoded);
            fsImage = new ITextFSImage(Image.getInstance(decodedBytes));
        } else {
            fsImage = uac.getImageResource(srcAttr).getImage();
        }
        return fsImage;
    }

    public void remove(Element e) {}

    public void reset() {}

    @Override
    public void setFormSubmissionListener(FormSubmissionListener listener) {}
}

<img> 태그를 찾아 src 경로에있는 이미지 파일을 base64 로 변환해 리턴

6. String 을 PDF 로 출력

    public static void generateFromHtml(Map<String, Object> pMap,String templateCode,  String fileName) throws DocumentException, IOException {

//        String templateCode = "pdf_test";
        Map<String, Object> variables = new HashMap<>();
        variables.put("name", "User");

//        String filename = "oo 재직증명서.pdf"; // PDF 파일 확장자를 포함한 기본 파일명
        String directoryPath = userDir() + "/pdf/"; // 디렉터리 경로
        String filepath = directoryPath + fileName;

        File file = new File(filepath);

        // 경로가 없으면 생성한다.
        File directory = new File(directoryPath);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        // 해당 경로로 pdf 파일 작성됨
        FileOutputStream fos = new FileOutputStream(file);

        // 메서드 호출
        String result = PdfGenerator.parseHtmlFileToString(templateCode, variables);
//        System.out.println(result);

        // String 으로 변환된 HTML 내용을 렌더링하여 ITextRenderer 객체를 반환
        ITextRenderer renderer = getRenderer(result, pMap);

        renderer.createPDF(fos);
        fos.close();
    }

변수, 폰트, 이미지 설정이 끝난 string 을 pdf 로 변환해 저장.

728x90