새소식

PHP/PHP

PHP -dom pdf로 html -> pdf 변환

  • -

개요

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>

핵심 포인트

반드시 체크할 사항

  1. 폰트 파일 형식: TTF 권장 (OTF 지원 제한적)
  2. 폰트 글리프 확인: 폰트 파일에 한글 포함 여부
  3. 인코딩 명시: loadHtml() 두 번째 인자에 'UTF-8' 지정
  4. 경로 설정: chrootsetBasePath 설정

왜 CLI 명령어 대신 수동 등록인가?

# 이 방법은 사용하지 않음
php vendor/dompdf/dompdf/load_font.php

수동 등록의 장점:

  • 배포 환경에서 추가 설정 불필요
  • 코드로 관리되어 버전 관리 용이
  • CI/CD 파이프라인에서 별도 스크립트 실행 불필요

후기

한글이 기본적으로 지원되지 않는 부분은 이해가 되지만 css의 일부 문법이 사용할 수 없는 건 많이 아쉽다.. 추후에 꼭 DomPDF로 pdf를 생성해야만 하는 일이 생기질 않길 빌어야겠다.

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.