HTML을 PDF로 변환하는 것은 이론적으로는 쉬워 보입니다. 세련된 템플릿을 만들고 데이터를 넣으면, 웹 페이지와 픽셀 단위까지 똑같이 일치하는 문서가 나올 것이라 기대합니다. 하지만 현실에서 파이프라인은 종종 크래시, 글리프(glyph) 누락, 시각적 손상과의 매일 같은 싸움으로 변질됩니다. 최근 프로젝트 진행 중 세 가지 문제가 계속해서 발생했습니다. 특정 SVG 그래픽을 만날 때 iText가 완전히 붕괴되거나, 이모지가 빈 흰색 사각형으로 사라지거나, 미세한 투명 배경이 불투명한 검은색 블록으로 변하는 현상이었습니다. 각 실패에는 뚜렷한 원인이 있었으며, 이 세 가지를 모두 해결하려면 PDF 엔진이 콘텐츠를 확인하기 전에 애플리케이션이 콘텐츠를 준비하는 방식을 재고해야 했습니다.
SVG가 파이프라인을 망가뜨릴 때
iText는 편의를 위해 내부 SVG 렌더러를 포함하고 있지만, 이러한 통합 방식에는 치명적인 약점이 숨어 있습니다. SVG에 복잡한 경로(path), 무거운 CSS 스타일링 또는 특정 좌표 변환이 포함되어 있으면, 내장된 파서는 깔끔하게 예외를 던지고 넘어가는 대신 폭발해 버립니다. 이는 경고 없이 PDF 생성 스레드를 종료시키는 완전한 시스템 크래시로, 결과적으로 불완전한 파일과 벡터 파서 깊숙한 곳을 가리키는 스택 트레이스(stack trace)만을 남깁니다.
확실한 해결책은 iText에 SVG 렌더링을 아예 요청하지 않는 것입니다. 대신 그 작업을 스탠드얼론(standalone) 모드로 실행되는 Apache Batik으로 옮기십시오. Batik은 동일한 복잡한 경로와 CSS 규칙을 취약성 없이 처리하며, 이를 분리함으로써 PDF 엔진을 그래픽 관련 불안정성으로부터 보호할 수 있습니다. 워크플로우는 간단합니다. 문서 조립을 시작하기 전에 SVG를 Batik으로 실행하여 PNG 데이터 URL을 생성합니다. 그런 다음 원본 벡터 마크업 대신 해당 래스터 이미지를 iText에 전달합니다. 스탠드얼론 Batik은 더 큰 라이브러리 안에 묶여 고정된 내장 렌더러보다 SVG 사양을 더 밀접하게 따르며, 격리된 구조 덕분에 잘못된 그래픽이 전체 문서 변환을 중단시키지 않습니다.
작은 디테일 하나가 차트가 전문가 수준으로 보일지, 아니면 버그 보고서처럼 보일지를 결정합니다. SVG는 좌표계와 스케일링 동작을 정의하기 위해 viewBox 속성에 의존합니다. 변환 코드에서 viewBox를 무시하면, 완벽하게 유효한 차트가 읽을 수 없을 정도로 작아지거나 왜곡되어 늘어날 수 있습니다. 해당 속성을 명시적으로 파싱하고 그 치수를 출력 크기에 매핑하십시오. 이 단계를 건너뛰면 렌더링 품질과는 아무런 상관이 없고 오직 좌표 선언 누락 때문인 레이아웃 문제를 디버깅하는 데 수 시간을 허비하게 됩니다.
보이지 않는 잉크 문제
이모지가 있어야 할 자리에 나타나는 빈 사각형은 단순한 사실을 말해줍니다. 현재 폰트가 해당 언어를 지원하지 않는다는 것입니다. Helvetica를 비롯한 표준 PDF 폰트들은 이모지가 널리 사용되기 이전에 만들어졌습니다. 이 폰트들은 이모지 Unicode 범위에 대한 글리프를 포함하고 있지 않으므로, iText가 해당 코드 포인트를 만나면 아무것도 렌더링하지 않고 그냥 넘어갑니다. 그 결과 문서는 빈 상자로 가득 차게 되며, 소셜 감성 분석 보고서나 사용자 피드백 내보내기 결과물이 깨져 보이게 됩니다.
클라이언트 운영 체제가 이 간극을 메워줄 것이라고 기대해서는 안 됩니다. PDF는 자체적인 폰트 리소스를 포함하며, 브라우저에서 올바르게 보이더라도 파일이 시스템 폰트와 분리되면 아무런 의미가 없습니다. 해결책은 명시적인 폰트 라우팅 레이어를 구축하는 것입니다. 이모지 Unicode 블록을 커버하는 단색 심볼을 제공하는 Symbola와 같은 이모지 전용 폰트를 등록하십시오. 흑백 하트나 경고 심볼이 화려한 컬러 글리프 세트만큼 세련되지는 않더라도 의미는 전달합니다. 빈 사각형은 실패를 전달할 뿐입니다. 풀 컬러 이모지 폰트는 PDF 뷰어 내에서 일관되게 렌더링하기 여전히 어렵고, 컬러 지원을 쫓다 보면 해결되는 문제보다 호환성 문제가 더 많이 발생하곤 합니다.
iText는 줄 바꿈(line breaking)을 통해 두 번째로 더 까다로운 문제를 일으킵니다. 라이브러리가 이모지 서로게이트 페어(surrogate pairs)를 잘못된 경계에서 분리하여, 하나의 문자를 두 개의 유효하지 않은 절반으로 찢어버릴 수 있습니다. 이런 일이 발생하면 텍스트 스트림이 손상되어 하나의 글리프가 있어야 할 자리에 읽을 수 없는 파편들이 남게 됩니다. 이를 방지하려면 서로게이트 페어를 인식하고 이를 원자적 단위(atomic units)로 취급하는 커스텀 ISplitCharacter를 구현하십시오. 이렇게 하면 레이아웃 엔진이 이모지 중간에 줄 바꿈을 삽입하는 것을 막고 텍스트의 무결성을 유지할 수 있습니다.
투명도가 검은색으로 변할 때
부드러운 rgba 배경이나 레이어드 fill-opacity 효과가 적용된 SVG는 브라우저에서 세련되게 보입니다. 하지만 동일한 마크업을 iText에 넣으면 투명도가 종종 불투명한 검은색 사각형으로 뭉개져 버립니다. 엔진이 CSS 컬러 함수와 opacity 속성을 잘못 처리하여, 투명도 대신 불투명한 잉크를 채워 넣기 때문입니다.
컨버터에 도달하기 전에 SVG를 전처리하는 것이 유일하고 확실한 방어책입니다. 알파 블렌딩(alpha blending)에 의존하는 모든 요소를 제거하거나 교체하십시오. rgba() 값을 불투명한 rgb() 색상으로 변환하십시오. 투명도 개념을 반드시 유지해야 한다면, CSS 단축 속성에서 값을 빼내어 표준 opacity 속성으로 옮기십시오. 하지만 투명도를 완전히 제거하는 것이 가장 안전한 방법입니다. 이러한 변경 사항은 웹 디자인 측면에서 퇴보하는 것처럼 느껴질 수 있지만, PDF는 현대적인 CSS 투명도보다 앞선 다른 이미징 모델을 사용합니다. PDF 형식은 명확한 색상 값을 기대하며, 모호한 값을 제공하면 문제가 발생할 가능성이 큽니다.
마크업을 정화(sanitizing)하는 동안, 모든 SVG에 적절한 xmlns 네임스페이스 선언이 포함되어 있는지 다시 한번 확인하십시오. 생성된 HTML과 템플릿 엔진은 종종 미니피케이션(minification)이나 DOM 직렬화(serialization) 과정에서 네임스페이스 속성을 누락시키곤 합니다. 해당 네임스페이스가 없으면 SVG 파서가 요소를 잘못 식별하거나 오류를 조용히 넘겨버려, 파서 에러가 발생하거나 페이지에 제대로 표시되지 않는 손상된 벡터 데이터를 생성할 수 있습니다. 이는 단 몇 초면 끝나는 기본적인 확인 작업이지만, 수 시간의 작업 시간을 아껴줍니다.
하나의 템플릿, 두 개의 세계
최악의 장기적 해결책은 브라우저용과 PDF용 HTML 템플릿을 별도로 유지하는 것입니다. 레이블이 어긋나고 여백이 변하면서, 곧 내보낸 보고서가 대시보드와 일치하지 않게 됩니다. 더 깔끔한 아키텍처는 단일 템플릿에 의존하며, context.isForPdf()와 같은 단일 플래그를 통해 렌더링 로직을 분기합니다.
해당 플래그가 false일 때, 템플릿은 완전한 브라우저 경험을 제공합니다. 무한 줌을 위한 네이티브 SVG, 현대적인 CSS, 그리고 브라우저가 지원하는 모든 색상 에셋을 제공합니다. 플래그가 true일 때, 동일한 템플릿은 SVG 에셋을 미리 렌더링된 PNG로 교체하고, 이모지에 안전한 폰트 스택을 활성화하며, 지원되지 않는 투명도 효과를 제거합니다. 텍스트와 구조는 변경되지 않으며, 오직 에셋 파이프라인과 스타일 규칙만 대상 매체에 맞춰 조정됩니다.
이러한 이중 경로(dual-path) 접근 방식은 코드베이스의 일관성을 유지해 줍니다. 콘텐츠는 한 곳에서만 업데이트하면 되며, 라우팅 레이어가 화면과 종이 사이의 기계적인 차이를 처리합니다. 또한 테스트를 더 단순하게 만듭니다. 브라우저의 개발자 도구를 사용하여 템플릿 로직을 검증한 다음, PDF 플래그를 활성화하여 동일한 데이터가 컨버터를 중단시키지 않고 깔끔한 문서를 생성하는지 확인할 수 있습니다.
PDF 생성에 관한 냉혹한 진실
PDF는 결코 브라우저처럼 동작하지 않습니다. 렌더링 모델이 근본적으로 다르며, iText와 같은 라이브러리는 속도, 파일 크기, 사양 준수 사이에서 의도적인 절충(trade-off)을 합니다. 성공은 엔진과 싸우며 요행을 바라는 데서 오는 것이 아닙니다. 한계를 조기에 인정하고 그 한계를 중심으로 파이프라인을 설계하는 데서 옵니다.
PDF 단계 이전에 벡터를 변환하십시오. 모든 글리프(glyph)가 대체 폰트를 가질 수 있도록 폰트를 명시적으로 지정하십시오. 투명도를 제거하고 단색으로 만드십시오. 템플릿이 어떤 환경을 위해 렌더링하는지 알 수 있도록 필요한 컨텍스트를 제공하십시오. 이를 일관되게 수행하면, 문서는 더 이상 렌더러와 싸우지 않고 의도한 모습 그대로 나타날 것입니다.
