개요
DomPDF로 PDF를 생성할 때 한글이 깨지는 문제가 발생하여 폰트 등록부터 인코딩 문제까지, 여러 시행착오를 겪어가며 해결하였다. 하지만 DomPDF로 PDF를 생성하는 방법은 font 관련 css를 넣었을때 또 깨지는 문제가 발생하고 일부 css가 먹질 않아서 폐기하게 되었다. 그리하여 결국에는 이 방식을 사용하지 않게 되었지만 그냥 갖다버리기는 아까워서 글로라도 남기려한다...
DomPDF 한글 깨짐 문제
문제의 원인
DomPDF는 기본 폰트(DejaVu Sans)에 한글 글리프가 없다 . 한글을 표시하려면 한글을 지원하는 폰트를 명시적으로 등록해야 한다.
증상별 원인
? (물음표): 인코딩 문제 또는 폰트 미등록
□ (네모): 폰트에 해당 글리프 없음
깨진 문자: UTF-8 인코딩 미지정
해결 과정
1단계: 인코딩 확인
$this->dompdf->loadHtml($html, 'UTF-8'); // 명시적 인코딩 지정
2단계: 한글 지원 폰트 준비
Pretendard, Noto Sans KR, 나눔고딕 등
반드시 TTF 형식 사용 (OTF는 제한적)
폰트 파일을 public/fonts/에 배치
3단계: 폰트 수동 등록
$fontMetrics = $this->dompdf->getFontMetrics();
$fontMetrics->registerFont([
'family' => 'Pretendard',
'style' => 'normal',
'weight' => 'normal'
], public_path('fonts/Pretendard.ttf'));
최종 구현 코드
PdfHelper 클래스
<?php
namespace App\Helpers;
use Dompdf\Dompdf;
use Dompdf\Options;
use Illuminate\Support\Facades\View;
class PdfHelper
{
private Dompdf $dompdf;
public function __construct()
{
$options = new Options();
// HTML5 파서 활성화: 최신 HTML5 태그 및 속성 지원
$options->set('isHtml5ParserEnabled', true);
// 원격 리소스 비활성화: 외부 URL(이미지, CSS 등) 로드 차단 (보안)
$options->set('isRemoteEnabled', false);
// 파일 접근 루트 경로 설정: public/ 디렉토리로 제한하여 보안 강화
$options->set('chroot', public_path());
$this->dompdf = new Dompdf($options);
$this->dompdf->setBasePath(public_path());
// 폰트 수동 등록
$fontMetrics = $this->dompdf->getFontMetrics();
$fontMetrics->registerFont([
'family' => 'Pretendard',
'style' => 'normal',
'weight' => 'normal'
], public_path('fonts/PretendardVariable.ttf'));
}
/**
* pdf 파일을 생성하는 메서드
* @param string $view pdf로 변환할 html 파일명
* @param array $data html 내에 삽입할 데이터
* @return $this pdf 객체를 render한 helper 객체
*/
public function generate(string $view, array $data = []): self
{
$html = View::make($view, $data)->render();
$this->dompdf->loadHtml($html);
$this->dompdf->setPaper('A4', 'portrait');
$this->dompdf->render();
return $this;
}
/**
* 생성한 pdf 파일을 로컬에 다운로드하는 메서드
* @param string $filename 설정할 pdf 파일명
*/
public function download(string $filename)
{
return response()->streamDownload(
fn() => print($this->dompdf->output()),
$filename,
['Content-Type' => 'application/pdf']
);
}
/**
* 생성한 pdf 파일을 브라우저에 뷰어로 노출하는 메서드
* @param string $filename 설정할 pdf 파일명
*/
public function stream(string $filename)
{
return response($this->dompdf->output(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="' . $filename . '"',
]);
}
}
사용 예시
// 컨트롤러에서
public function downloadPdf()
{
$pdfHelper = new PdfHelper();
return $pdfHelper
->generate('pdf.template', ['link' => 'https://example.com'])
->download('document.pdf');
}
HTML 템플릿
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
body {
font-family: 'Pretendard', sans-serif;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h1>한글 제목</h1>
<p>본문 내용입니다.</p>
</body>
</html>
핵심 포인트
반드시 체크할 사항
폰트 파일 형식 : TTF 권장 (OTF 지원 제한적)
폰트 글리프 확인 : 폰트 파일에 한글 포함 여부
인코딩 명시 : loadHtml() 두 번째 인자에 'UTF-8' 지정
경로 설정 : chroot와 setBasePath 설정
왜 CLI 명령어 대신 수동 등록인가?
# 이 방법은 사용하지 않음
php vendor/dompdf/dompdf/load_font.php
수동 등록의 장점:
배포 환경에서 추가 설정 불필요
코드로 관리되어 버전 관리 용이
CI/CD 파이프라인에서 별도 스크립트 실행 불필요
후기
한글이 기본적으로 지원되지 않는 부분은 이해가 되지만 css의 일부 문법이 사용할 수 없는 건 많이 아쉽다.. 추후에 꼭 DomPDF로 pdf를 생성해야만 하는 일이 생기질 않길 빌어야겠다.