번개애비의 라이프스톼일

Go Lang 으로 HTML 을 PDF로 변환하기 (wkhtmltopdf 를 사용하지 않고) 본문

IT

Go Lang 으로 HTML 을 PDF로 변환하기 (wkhtmltopdf 를 사용하지 않고)

번개애비 2021. 10. 1. 15:33

프로젝트에서 HTML을 PDF로 변환하여 고객에게 제공하는 기능구현을 하기 위해 서칭을 했고, 여러가지 솔루션을 찾게 되었다.

 


 

수 많은 시행착오들...

커멘트에서 동작하는 wkhtmltopdf 는 커멘트라인에서 실행됨으로 웹서비스에서는 보안상이유로 사용하지 않았다. (exec등)

Javascript에서 Canvas를 이용하여 PDF를 생성해주는 html2pdf 는 속도문제와 아이폰에서 Canvas 생성사이즈의 제한 (4K이하)의 문제가 있었다.

PHP에서는 TCPPDF라는것이 존재하지면 HTML문서를 정확하게 파싱이 불가능했다.

 

 


 

결국 구글신이 해결해주심

 

결국 chromedp 를 사용하기에 이르렀고 그나마 안정적이면서 정확하게 PDF생성이 가능해서 서비스에 적용했다.

PDF를 출력하는 기능자체가 메인서비스가 아닌 부가적인 서비스임으로 마이크로서비스를 위해 GO로 작성했다.

(도커나 클라우드에 독립된 기능으로 손쉽게 분리가 가능하고 버전관리가 용이해서 마이크로서비스는 Go가 대세인듯하다.)

 

서버 환경은 CentOS LTS버전이다.

GUI가 아닌 LTS 버전에서 테스트했음으로 우분투나 도커등등도 동일하게 동작이 가능할것(?)이다.

 

 

 


Lets Go

 

 

1. 크롬설치

chromedp를 사용하기 위해 크롬브라우저를 설치한다.

repo로 굉장히 손쉽게 설치가 가능하다.

wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
sudo yum localinstall google-chrome-stable_current_x86_64.rpm

 

 

 

2. 폰트설치 (LTS환경만)

여기서 살짝 삽질을 했다.

chromedp에서 PDF출력을 했을때 이미지나 표는 잘 출력이 가능했지만 text가 출력되지 않았다. 

알고보니 브라우저에서 지원되는 폰트가 존재하지 않았기 때문이었다. 한글 PDF를 출력하기 위해서는 한글을 지원하는 나눔고딕이나 애플산들고딕등을 폰트설치를 해줘야 한다.

애플산들고딕 저용량버전 : https://www.burndogfather.com/242 

 

애플 산들 고딕체 저용량 경량 웹폰트 (Apple SD Gothic)

저작권관련사항 : "애플산돌네오고딕과 관련해서는 산돌에 어떠한 권리도 없고 단순 제작 의뢰를 받아 만들어줬을 뿐이다. 라이선스 정책은 애플에 문의하고 그에 따르면된다. 마이크로소프트

www.burndogfather.com

 

아래 경로가 리눅스에서 폰트를 저장하는 공간이다.

아래 경로에 폰트파일을 넣어두고,

cd /usr/share/fonts/

아래명령어를 실행하면 폰트를 설치할 수 있다.

fc-cache -r

 

 

 

3. Go Code

(Go lang 설치는 넘나 쉬우니 생략...)

chromedp를 설치해해주고,

go get -u github.com/chromedp/chromedp

아래코드를 작성하고 go run main.go 를 실행한다.

package main
import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"time"
	"github.com/chromedp/cdproto/emulation"
	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {
	taskCtx, cancel := chromedp.NewContext(
		context.Background(),
		chromedp.WithLogf(log.Printf),
	)
	defer cancel()
	taskCtx, cancel = context.WithTimeout(taskCtx, 15*time.Second)
	defer cancel()
	var pdfBuffer []byte
	if err := chromedp.Run(taskCtx, pdfGrabber("https://www.naver.com", "body", &pdfBuffer)); err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile("naver.pdf", pdfBuffer, 0644); err != nil {
		log.Fatal(err)
	}
}

func pdfGrabber(url string, sel string, res *[]byte) chromedp.Tasks {

	start := time.Now()
	return chromedp.Tasks{
		emulation.SetUserAgentOverride("WebScraper 1.0"),
		chromedp.Navigate(url),
		chromedp.WaitVisible(`body`, chromedp.ByQuery),
		chromedp.ActionFunc(func(ctx context.Context) error {
			buf, _, err := page.PrintToPDF().WithPrintBackground(true).Do(ctx)
			if err != nil {
				return err
			}
			*res = buf
			fmt.Printf("\nDuration: %f secs\n", time.Since(start).Seconds())
			return nil
		}),
	}
}

 

 

요로코롬 PDF를 생성하기 위한 시간이 출력되면서 naver.pdf 파일이 생성된다.

 

 

네이버 광고영역은 여기선 나타나지 않지만 visible 설정을 통해 출력이 가능할것으로 보인다.

chromedp.WaitVisible(`body`, chromedp.ByQuery)

 

 

유튜브를 예시로 들면...

왼쪽은 WaitVisible 를 body로 설정한 반면 오른쪽은 .video-stream 에 설정한 화면이다.

HTML DOM이 생성된이후 추가적인 Display 처리하는 동적요소까지 반영하여 PDF를 만들수 있다.

 

 

대표적인 chromedp의 명령셋은 다음과 같다.

보다 자세한 설명은 https://github.com/chromedp/examples 참조

//URL이동
chromedp.Navigate(`https://www.google.com`)

//2초 동안 쉬기
chromedp.Sleep(2 * time.Second)

//로드가 될때까지 기다리기
chromedp.WaitVisible(`#hplogo`, cdp.ByID)

//키보드입력
chromedp.SendKeys(`#lst-ib`, q+"\n", cdp.ByID)

//클릭하기
chromedp.Click(`#hplogo`)

 

Comments