.ai 파일을 PNG로 바꿨더니 한글만 사라졌어요

아무 말 없이 비어버린 자리

.ai를 PNG로 바꾸는 도구를 만들던 중에 실제 클라이언트 파일을 한 번 통과시켜 봤어요. 아트보드 여러 장짜리 한글 포스터 레이아웃이었고, 영문과 한글이 섞여 있었죠. 나온 PNG는 얼핏 멀쩡해 보였어요. 도형도 맞고, 앉힌 사진도 맞고, 영문 헤드라인도 그대로였어요. 그런데 한글이 한 글자도 없었어요.

깨진 것도 아니고, 네모 상자로 바뀐 것도 아니고, 위치가 밀린 것도 아니었어요. 애초에 그 텍스트 프레임을 그린 적이 없는 것처럼 그냥 없었어요. 콘솔에 에러 한 줄 안 떴고, 화면에 경고도 없었고, 파일 자체는 멀쩡했어요. 일러스트레이터에서는 아무 문제 없이 열렸거든요.

이 조용함이 이 문제를 굳이 글로 남기는 이유예요. 변환이 잘못되면 보통은 뭐라도 티가 나요. 그런데 이 버그는 겉보기에 말끔한, 구멍 하나 뚫린 이미지를 내놓아요. 변환하는 사람이 그 언어를 못 읽는 상황이라면 그대로 넘어갈 가능성이 꽤 높고요.

빠진 건 CMap이라는 대응표예요

원인을 알려면 .ai 파일 속을 한 번 봐야 해요. 일러스트레이터는 대부분의 파일을 “PDF 호환 파일 만들기”를 켠 채로 저장하는데, 이 옵션이 .ai 안에 완전한 PDF 버전을 통째로 넣어둬요. 일러스트레이터가 아닌 프로그램이, 브라우저를 포함해서, 실제로 읽는 건 이 PDF 데이터예요. 구조는 일러스트레이터 없이 .ai 파일 여는 방법에서 더 자세히 다뤘어요.

PDF 안에서 영문 폰트는 대체로 단순해요. 글자 하나에 1바이트를 쓰고, 파일 안에 든 인코딩 표가 “0x41은 A의 글리프”라고 알려줘요. 이 표는 작아서 PDF 안에 그냥 들어가요.

한중일 폰트는 그럴 수가 없어요. 한글 폰트는 음절만 수천 자고, 일본어는 수만 자, 중국어는 더 많거든요. 그래서 PDF는 CID 방식 폰트를 써요. 글자를 1바이트로 가리키는 대신, 문자 모음(Adobe-Korea1, Adobe-Japan1, Adobe-GB1, Adobe-CNS1) 안의 글리프 번호로 가리키는 방식이에요. 파일 속 바이트와 이 번호를 이어주는 대응표가 바로 CMap이고요. 한글은 UniKS-UTF16-H, 일본어는 UniJIS-UCS2-H, 중국어 간체·번체는 UniGB-와 UniCNS- 계열처럼 이름으로 지정돼요.

핵심은 여기예요. PDF는 CMap의 이름만 적어둬요. 표 자체는 들어 있지 않아요. 읽는 쪽이 이미 갖고 있으리라고 전제하는 구조예요.

그러니 렌더러가 한글 문장을 그리려고 할 때, 받은 적 없는 대응표를 찾다가 아무것도 못 얻고, 글리프를 0개 찾아내서, 0개를 그려요. 렌더러 입장에서는 실패한 게 아니에요. 그릴 게 없었을 뿐이죠. 그래서 에러도 안 나요.

브라우저 도구에서 특히 잘 터지는 이유

브라우저와 웹 도구가 PDF를 읽을 때 쓰는 pdf.js는 이 CMap 표들을 메인 스크립트에 넣지 않고 별도의 작은 바이너리 파일 묶음으로 배포해요. 합리적인 기본값이에요. 표가 꽤 크고, 영문 청구서만 띄우는 뷰어가 페이지마다 한중일 대응표까지 받아올 이유는 없으니까요.

문제는 그래서 따로 알려주지 않으면 pdf.js가 CMap을 아예 쓰지 않는다는 점이에요. 저희가 사이트에 올려둔 건 .bcmap 파일 168개고, PDF 지원 자료 전체로는 198개 파일이에요. 켜는 데 필요한 건 문서를 여는 호출에 두 줄 넣는 것뿐이고요.

const task = pdfjs.getDocument({
  data: buffer,
  cMapUrl: '/pdf-assets/cmaps/',
  cMapPacked: true,
  standardFontDataUrl: '/pdf-assets/standard_fonts/',
});

이걸 빼도 빌드는 되고, 실행도 되고, 렌더링도 돼요. 한중일 텍스트가 든 파일을 누가 변환하기 전까지는요. 영문만으로 짜인 테스트로는 절대 안 잡혀요. 저희도 만들어낸 샘플이 아니라 실제 한글 디자인 파일로 확인했기 때문에 잡았고, 지금은 이 파라미터를 왜 지우면 안 되는지 소스 주석에 못 박아 뒀어요.

같은 벽에 다른 경로에서 한 번 더 부딪혔어요. .ai를 SVG로 내보내는 기능은 SVG 출력 백엔드가 최신 버전에서 빠져서 구버전 pdf.js 위에서 돌아가는데, 이쪽도 같은 이유로 자기 몫의 cMapUrl이 필요했어요. 여기에 폰트 데이터를 남겨두는 옵션도 같이 넣어야 했고요. 이게 없으면 SVG에 폰트를 심는 단계에서 조용히 넘어가는 대신 예외가 나요. 같은 종류의 문제를, 서로 다른 렌더러 두 개에서 각각 따로 막아야 했던 셈이에요.

증상 세 가지, 원인도 세 가지

변환한 뒤 글자가 이상하다면, 증상만 봐도 원인이 꽤 좁혀져요.

한중일 글자만 전부 없고 나머지는 멀쩡해요. 앞에서 설명한 CMap 누락이에요. 같은 파일의 영문은 잘 나오는데, 단순 폰트는 인코딩 표를 PDF 안에 갖고 있기 때문이에요. 프레임이나 레이어 단위가 아니라 문자 체계 단위로 골라서 사라지는 게 이 버그의 지문이에요.

글자는 있는데 서체가 달라 보여요. 폰트가 파일에 포함되지 않아서 렌더러가 비슷한 폰트로 대체한 거예요. 자간이 틀어지고 줄바꿈 위치가 바뀌면서 디자인이 흐트러지지만, 사라지지는 않아요. 이건 폰트 문제라 CMap을 아무리 챙겨도 안 고쳐져요. 보낸 쪽에서 폰트를 포함해 저장하거나 윤곽선으로 만들어 줘야 해요.

아무것도 안 나오고 빈 화면이나 안내 문구만 떠요. “PDF 호환 파일 만들기”를 끈 채로 저장한 파일이라 읽을 PDF 데이터 자체가 없어요. 이건 일러스트레이터에서 옵션을 켜고 다시 저장하는 수밖에 없어요.

순서대로 확인해 보면 좋아요. 첫 번째는 해당 언어를 읽지 못하면 눈에 안 띄니까요.

파일을 넘길 때 미리 막는 방법

습관 몇 개면 이 부류의 문제는 통째로 사라져요.

  • 이미지 변환용으로 넘길 파일은 텍스트를 윤곽선으로 만들어 주세요. 글자가 패스가 되는 순간 폰트도, 인코딩도, CMap도 계산에서 빠지고 그냥 도형만 남아요. 대신 수정용 원본은 따로 남겨두세요. 윤곽선으로 만든 글자는 다시 못 고치니까요.
  • 내보낸 이미지를 열어서 글자가 있는지 눈으로 확인하세요. 특히 읽지 못하는 언어일수록요. 글리프를 조용히 빠뜨리는 변환기는 대충 보면 통과하는 이미지를 만들어내요.
  • 변환 도구는 실제로 쓰는 언어의 파일로 시험해 보세요. 영문 테스트 파일은 한글 처리에 대해 아무것도 증명해 주지 않아요. 직접 띄운 간단한 변환 도구에 이 버그가 오래 살아남는 이유이기도 해요.
  • 한글이 날아갔다면 파일이 망가진 게 아니라 도구 설정이 빈 경우가 대부분이에요. 파일은 멀쩡하고, 읽는 쪽이 준비가 덜 된 거예요.

저희 변환기는 어떻게 처리하나요

PNG, SVG, PDF, 뷰어 — .ai 도구 네 가지 모두 렌더링 경로 양쪽에서 CMap 전체를 불러와요. 그래서 한글·일본어·중국어가 사라지지 않고 그대로 그려져요. 파일에 포함되지 않은 표준 폰트를 위한 대체 데이터도 같이 불러오는데, 이게 “비슷한 글자꼴이라도 나오는 것”과 “글자가 아예 안 나오는 것”의 차이예요.

솔직하게 남는 한계도 두 가지 있어요. 파일에 포함되지 않은 폰트는 여전히 비슷한 폰트로 대체되기 때문에 원래 디자인과 다르게 보일 수 있어요. 이건 파일 쪽 한계라 렌더러가 해결할 수 있는 문제가 아니에요. 그리고 CMYK 색상은 화면 표시를 위해 RGB로 바뀌니까, 인쇄 색이 중요한 작업이라면 일러스트레이터에서 한 번 더 확인해 주세요.

같은 테스트에서 함께 잡은 다른 버그도 이 일이 얼마나 실측에 기대는지 보여주는 예라 적어둘게요. 지금 쓰는 pdf.js 버전은 배경 파라미터를 불투명하게 칠해서, 투명 배경을 달라고 하면 검정이 나와요. 그래서 저희 PNG 내보내기는 아트보드마다 흰 배경과 검은 배경으로 두 번 렌더링한 뒤, 두 결과의 차이에서 픽셀별 투명도를 계산하고 색을 되돌리는 방식으로 투명 배경을 복원해요. 필요 이상으로 번거로운 방법이고, 기준 렌더 결과와 픽셀 단위로 비교하다가 어긋난 걸 발견해서 넣은 코드예요.

두 수정 모두 같은 데서 나왔어요. 말끔해 보이는 결과를 맞는 결과라고 믿는 대신, 실제 디자인 파일을 도구에 통과시켜 본 것뿐이에요. 어느 쪽이든 처리는 전부 브라우저 안에서 끝나고 파일이 서버로 전송되지 않아요. 그러니 외부에 나가면 안 되는 클라이언트 파일로 직접 시험해 봐도 드는 비용은 시간뿐이에요.