← 모두의 툴

PDF 워터마크에 한글이 깨지는 이유 — 내장 폰트의 한계

가이드 · 2026-08-19 최종 확인

PDF 워터마크 도구에 "대외비"라고 입력했는데 결과물엔 빈 사각형(□) 몇 개만 찍혀 나온 경험, 드물지 않습니다. 브라우저에서 즉석으로 PDF에 텍스트를 그려 넣는 도구 다수가 겪는 구조적 한계인데, 원인은 PDF 규격 자체에 있습니다.

1. PDF 표준 14개 기본 폰트란

PDF 규격(ISO 32000)은 Helvetica, Helvetica-Bold, Times-Roman, Courier 등 14종의 "표준 14개 폰트(Standard 14 Fonts)"를 정의합니다. 이 폰트들의 특별한 점은, PDF 파일 안에 폰트 데이터를 직접 담지 않아도 된다는 것입니다 — PDF를 여는 모든 뷰어(Acrobat, 브라우저 내장 뷰어, 프린터 드라이버 등)가 이 14개 폰트를 자체적으로 내장하고 있다고 규격 자체가 보장하기 때문입니다. 덕분에 파일 용량이 작고, 어떤 환경에서 열어도 폰트가 깨질 걱정이 없습니다.

2. 문제: 이 14개 폰트는 전부 라틴 문자 전용

표준 14개 폰트는 WinAnsiEncoding·MacRomanEncoding이라는 인코딩 표를 기반으로 하는데, 이 표에 담긴 글리프(실제 그릴 수 있는 문자 모양)는 서유럽 라틴 알파벳·숫자·기호가 전부입니다. 한글(한글 음절 11,172자), 한자, 가나 글리프는 애초에 이 14개 폰트 안에 존재하지 않습니다. 그래서 워터마크 텍스트에 한글을 넣으면, 렌더러가 "이 코드에 대응하는 글리프가 없다"는 상태가 되어 빈 사각형이나 깨진 문자로 표시되는 것입니다 — 폰트가 손상된 게 아니라, 애초에 그릴 방법이 없는 것입니다.

3. 이 도구가 실제로 쓰는 코드

이 사이트의 PDF 워터마크 도구는 pdf-lib 라이브러리로 각 페이지 위에 텍스트를 직접 그려 넣는 방식이며, 실제 소스 코드를 보면 다음과 같이 표준 폰트만 사용합니다.

const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
별도 폰트 파일을 로드하는 코드 없이, PDF 표준에 내장된 Helvetica-Bold만 그대로 사용합니다. 이 도구의 자체 FAQ에도 "한글 워터마크는 지원되지 않음"이라고 명시돼 있습니다.

4. 해결책: 커스텀 폰트 임베딩

한글을 실제로 렌더링하려면 Noto Sans KR, 나눔고딕 같은 한글 글리프가 포함된 TTF/OTF 폰트 파일 자체를 PDF 안에 통째로 심어(임베딩) 넣어야 합니다. pdf-lib 생태계에서는 이를 위해 별도의 @pdf-lib/fontkit 라이브러리가 필요합니다 — pdfDoc.registerFontkit(fontkit)으로 등록한 뒤 pdfDoc.embedFont(fontBytes)로 실제 폰트 파일(바이트)을 임베딩해야, 그 PDF는 어떤 기기에서 열어도 한글 글리프 자체를 자체 보유한 자기완결적 파일이 됩니다. 이 과정은 표준 폰트를 쓸 때보다 코드도, 파일 용량도 늘어나는 트레이드오프가 있습니다.

5. 실무 대응

자주 묻는 질문

Q. 이 워터마크 도구로 한글을 넣을 수 있나요?

A. 안 됩니다. PDF 표준 내장 폰트(Helvetica-Bold)만 사용해 한글 글리프가 없기 때문에, 입력하면 빈 사각형이나 깨진 문자로 표시됩니다. 영문 단어를 사용하세요.

Q. 왜 이 도구는 처음부터 한글 폰트를 넣지 않았나요?

A. 폰트 파일 임베딩은 코드 복잡도와 파일 용량을 늘리는 트레이드오프입니다. 표준 14개 폰트만 쓰면 별도 파일 로드 없이 가볍고 빠르게 동작하는 대신, 라틴 문자만 지원하는 한계를 감수하는 설계입니다.

Q. 다른 PDF 도구는 한글이 되던데, 왜 다른가요?

A. 그런 도구는 fontkit 등을 이용해 한글 폰트 파일 자체를 PDF에 임베딩하는 추가 작업을 구현한 것입니다. 모든 PDF 생성 도구가 기본으로 지원하는 기능이 아니라, 별도로 구현해야 하는 기능입니다.

Q. 빈 사각형(□) 대신 아예 글자가 사라지는 경우도 있나요?

A. 라이브러리·설정에 따라 다릅니다. 지원하지 않는 글리프를 만나면 인코딩 오류를 던지거나, 조용히 건너뛰거나, 빈 사각형으로 대체하는 등 구현마다 처리 방식이 다릅니다.