HotPDF는 ImportSVGFormXObject를 통해 SVG 파일을 로드된 PDF 또는 작성 중인 PDF에 재사용 가능한 Form XObject로 가져오며, 이 함수는 생성된 폼의 리소스 이름을 반환합니다. 배치는 별도의 호출인 DrawSVGFormXObject로 이루어지므로, 한 번의 가져오기만으로 아트워크 바이트를 단 하나도 복제하지 않고 여러 페이지에 서로 다른 크기로 찍을 수 있습니다
이 분리는 겉보기보다 훨씬 중요합니다. 300 DPI로 래스터화한 로고를 400페이지에 배치하면 그것은 비트맵의 400개 사본이 됩니다. 같은 로고를 Form XObject로 다루면 하나의 객체가 400번 참조될 뿐이며, 어떤 배율로 확대해도 선명함을 유지하고, 빌드 시점에 충분해 보였던 DPI가 아니라 디바이스 해상도로 인쇄됩니다
매번 SVG를 그리는 대신 Form XObject를 쓰는 이유는?
Form XObject는 PDF 자체가 제공하는 재사용 가능한 콘텐츠 메커니즘으로, 자체 바운딩 박스와 리소스 딕셔너리를 가진 독립적인 콘텐츠 스트림이며 이름으로 호출됩니다. SVG를 여기에 가져오면 직접 그리기에는 없는 세 가지 특성을 얻게 됩니다. 아트워크는 파일 안에 한 번만 존재합니다. 각 배치는 작은 행렬 하나와 호출 하나에 불과합니다. 그리고 도형은 벡터로 유지되므로, 800퍼센트로 확대한 뷰어는 픽셀이 아니라 곡선을 그립니다
미묘한 부분은 리소스입니다. 폼의 폰트와 그래픽 상태는 가져오기 시점에 우연히 현재 페이지였던 곳의 리소스가 아니라, 폼 자체의 /Resources 딕셔너리 안에 있어야 합니다. HotPDF는 폼에 필요한 Base 14 폰트와 ExtGState 항목을 폼 자체에 기록하는데, 최초 호스트 페이지에서 이름을 빌려 쓰는 폼은 그 페이지에서는 올바르게 렌더링되지만 이후 배치되는 다른 모든 페이지에서는 해결되지 않는 리소스 이름을 만들어내기 때문입니다
두 번의 반전, 하나는 지오메트리용, 하나는 글리프용
SVG의 좌표계는 왼쪽 위 모서리에서 아래로 갈수록 커지지만, PDF Form XObject는 PDF 사용자 공간을 사용하며 이는 왼쪽 아래 모서리에서 위로 갈수록 커집니다. 루트 viewBox 변환은 한 번의 Y축 반전을 수행하여 지오메트리가 올바른 위치에 놓이도록 합니다. 이미 반전된 프레임 안에 그려진 글리프는 거꾸로 뒤집힌 모양이 되므로, 텍스트에는 두 번째로 로컬 반전이 하나 더 필요합니다
이는 PDF 페이지를 SVG로 내보낼 때 반대 방향으로 나타나는 것과 동일한 보정으로, PDF 페이지를 SVG로 내보내기에서 설명합니다. 이 처리를 잘못하면 텍스트가 있는 아트워크에서는 쉽게 눈에 띄지만 텍스트가 없는 아트워크에서는 거의 드러나지 않으므로, 로고 하나만 있을 때보다 태그라인이 붙은 로고가 더 나은 스모크 테스트가 됩니다
uses
HPDFDoc;
var
Pdf: THotPDF;
SvgBytes: TBytes;
FormName: AnsiString;
Stats: THPDFSVGImportStatistics;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalogue.pdf';
Pdf.BeginDoc;
SvgBytes := LoadFileBytes('brand-logo.svg');
FormName := Pdf.ImportSVGFormXObject(SvgBytes, Stats);
if FormName = '' then
raise Exception.Create('SVG rejected - see the import statistics');
// 한 번의 가져오기, 여러 번의 배치: 아트워크 바이트는 한 번만 존재함
for I := 1 to 40 do
begin
if I > 1 then
Pdf.AddPage;
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 780, 120, 40);
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
하이퍼링크는 어떻게 살아남는가?
하이퍼링크는 폼 내부에서 살아남을 수 없는데, Form XObject는 콘텐츠이고 PDF 링크 대상은 주석이며 주석은 페이지에 속하기 때문입니다. 따라서 원본 SVG의 <a> 요소를 폼의 콘텐츠 스트림에 그대로 복사할 수는 없습니다
HotPDF는 이 문제를, 파싱 중에 링크 사각형을 폼 공간 메타데이터로 보관해 두었다가 배치할 때마다 페이지 레벨 링크 주석으로 실체화하는 방식으로 해결합니다. DrawSVGFormXObject는 전달받은 대상 사각형을 통해 이 사각형들을 매핑하므로, 서로 다른 크기로 두 번 배치된 폼은 두 세트의 올바른 크기 히트 영역을 갖게 됩니다. 링크 사각형은 오직 루트 크기를 통해서만 매핑되고 콘텐츠 스트림에는 전혀 들어가지 않으므로, 그려지는 아트워크는 링크가 몇 개든 항상 동일하게 유지됩니다. 인쇄 전용 페이지처럼 상호작용 없이 아트워크만 필요할 때는 AddLinks = False를 전달합니다
임포터가 거부하는 것, 그리고 그것이 중요한 이유
SVG는 스크립팅, 외부 참조, 엔티티 확장을 포함하는 XML 방언이며, 이 때문에 임의의 SVG 파일은 임의의 HTML 파일과 마찬가지로 위험한 입력이 될 수 있습니다. 임포트 프로필은 문서 바깥으로 벗어나는 모든 것에 대해 fail-closed 방식을 취합니다: 외부 리소스 참조, 스크립트, 이벤트 핸들러 속성, DTD, 그리고 로컬 프래그먼트 참조가 아닌 모든 url()이 대상입니다
fail-closed란 문제가 되는 구문을 조용히 제거하는 대신 가져오기 자체를 실패시킨다는 의미입니다. 이 차이는 배치 파이프라인에서 중요한데, 조용한 제거는 아무도 모르게 무언가가 빠진 문서를 만들어내는 반면 거부는 격리해서 조사할 수 있는 파일을 만들어내기 때문입니다. 사용자로부터 SVG 업로드를 받는다면 이것이 바로 원하는 동작이며, 신뢰할 수 없는 이미지를 위한 코덱 프로세스 격리에서 설명하는 프로세스 레벨 격리와 함께 사용할 수 있습니다
컬러 폰트도 렌더링한다면 알아 둘 만한 내부 경계가 하나 있습니다. OpenType SVG 컬러 글리프에 사용되는 엄격한 SVG 하위 집합은 별도의 진입점을 거치며, 원래의 더 좁은 실패 동작을 그대로 유지합니다. 일반 아트워크 가져오기는 별개의 경로이므로, 가져온 아트워크에 호(arc), 투명도, CSS 지원을 추가해도 폰트 글리프에 허용되는 내용이 넓어지지는 않았습니다
그라디언트, 마스크, 그리고 래스터화 금지 원칙
그라디언트는 래스터화된 이미지가 아니라, 원본 지오메트리의 클리핑 경로로 제한된 바운디드 벡터 컬러 밴드로 가져와집니다. 마스크도 같은 방식을 사용합니다: 마스크 윤곽선은 벡터 클리핑 경로가 됩니다. 그 결과 그라디언트가 많은 일러스트레이션은 해상도에 독립적이면서도 작은 용량을 유지하며, 래스터화하는 임포터라면 가져오는 시점에 품질이 고정된 큰 이미지 객체를 만들어냈을 것입니다
대가는 극단적인 경우의 충실도입니다. 부드러운 그라디언트를 밴드로 근사한 결과는 SVG 그라디언트와 수학적으로 동일하지 않으며, 겹쳐진 소프트 마스크가 수십 개인 아트워크는 대신 고해상도 이미지로 렌더링하는 편이 원본에 더 가깝게 보일 것입니다. PDF가 네이티브로 지원하는 셰이딩 모델과 이를 직접 사용해야 할 때에 대해서는 축형 및 방사형 셰이딩 렌더링을 참고하세요
// 가져오기 통계를 읽으면 실제로 무엇이 통과했는지 알 수 있음
Writeln(Format('nodes=%d css rules=%d gradients=%d masks=%d',
[Stats.NodeCount, Stats.CSSRuleCount, Stats.GradientCount,
Stats.MaskCount]));
Writeln(Format('text runs=%d links=%d form bytes=%d',
[Stats.TextRunCount, Stats.LinkCount, Stats.OutputBytes]));
// 같은 폼을 두 가지 크기로 배치하면 두 개의 올바른 링크 세트가 생성됨
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);
// 인쇄 전용 배치: 히트 영역 없이 아트워크만 표시
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);
SVG 가져오기가 다른 벡터 경로들 사이에서 차지하는 위치
세 가지 벡터 가져오기 경로가 존재하며 서로 대체할 수 있는 것은 아닙니다. SVG 가져오기는 디자인 툴로 제작된 아트워크와 웹 파이프라인에서 넘어오는 모든 것에 적합합니다. EMF 및 WMF 벡터 가져오기에서 다루는 메타파일 가져오기는 Windows 애플리케이션과 디바이스 컨텍스트에 그리는 차트 컴포넌트가 생성하는 콘텐츠에 적합합니다. 페이지 API를 통한 직접 그리기는 중간 포맷이 전혀 없는, 자신의 코드가 계산하는 아트워크에 적합합니다
재사용성 논리는 특히 반복되는 요소에서 SVG 가져오기에 유리하게 작용합니다. 레터헤드, 로고, 워터마크, 인증 마크, 장식 룰은 모두 여러 페이지에서 동일하게 그려지는 요소이며, 이들 각각은 하나의 폼 객체와 페이지당 한 번의 호출로 처리됩니다. 그것이 별로 중요하지 않다고 판단하기 전에 실제 문서에서 파일 크기 차이를 측정해 보세요: 페이지마다 벡터 로고가 들어간 500페이지짜리 카탈로그라면, 하나의 폼과 500개의 인라인 사본 사이의 차이는 보통 수 메가바이트에 이릅니다
SVG 가져오기, 메타파일 가져오기, 직접 벡터 그리기는 Delphi와 C++Builder에서 동일한 콘텐츠 스트림 라이터를 공유합니다. 전체 기능 목록은 HotPDF Delphi PDF 컴포넌트 페이지에서 확인할 수 있습니다