PDFium Component는 AddCidType2Text를 사용해 PDF 페이지에 글리프 수준으로 텍스트를 기록하며, 사용자가 제공한 TrueType 데이터를 CID Type 2 폰트로 임베드합니다. 각 글리프 인스턴스에 순차적인 CID를 할당하고, 명시적인 CID-to-GID 맵과 ToUnicode CMap을 생성하며, 같은 베이스라인 위의 글리프들을 하나의 네이티브 텍스트 객체로 병합합니다. 선택적인 서브셋팅은 파일을 작게 유지하며, 서브셋팅이 실패했을 때 어떻게 할지에 대한 명시적인 정책도 있습니다
글리프 수준 기록은 텍스트가 이미 셰이핑되어 있을 때 필요합니다. 아랍어, 데바나가리, 그리고 문맥에 따라 형태가 바뀌는 모든 문자 체계는 더 이상 문자와 일대일로 대응하지 않는 글리프 식별자 시퀀스를 만들어내므로, 문자열과 폰트 이름을 받는 API로는 그 결과를 표현할 수 없습니다. 글리프와 위치를 직접 전달하는 것이 올바르게 셰이핑된 복잡한 문자 체계 텍스트를 PDF에 넣는 유일한 방법입니다
왜 모든 글리프 인스턴스가 자신만의 CID를 갖는가
매력적인 최적화는 중복 제거입니다: 구별되는 글리프 식별자마다 하나의 CID를 두고, 그 글리프가 나타나는 모든 곳에서 재사용하는 것입니다. 이는 더 작은 폰트를 만들어내지만 텍스트 추출을 망가뜨리는데, 같은 글리프가 위치에 따라 서로 다른 유니코드 내용에 정당하게 대응할 수 있기 때문입니다
합자 글리프가 명백한 사례입니다. 같은 'ffi' 글리프가 한 단어에서는 세 문자를 나타내고, 다른 셰이핑 결정 이후에는 다른 곳에서 다른 것을 나타낼 수 있습니다. ToUnicode 맵은 CID를 키로 삼으므로, 공유된 CID는 하나의 매핑만 가질 수 있고, 그 경쟁에서 진 쪽 텍스트는 추출할 수 없게 됩니다
그래서 각 글리프 인스턴스는 자신만의 CID를 가지며, 각 매핑은 몇 개의 UTF-16 코드 단위든 자유롭게 가질 수 있습니다. 의미 있는 텍스트가 없는 글리프 — 장식 요소, 위치 지정 전용 글리프 — 는 명시적으로 U+200B, 즉 폭이 0인 공백에 매핑되므로, 모든 CID는 빈틈이 아니라 관찰 가능한 추출 결과를 갖습니다
uses
PDFium;
var
Pdf: TPdf;
Glyphs: TPdfCidGlyphs;
Options: TPdfCidFontOptions;
Report: TPdfCidFontReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'label.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 1;
// 셰이핑된 글리프마다 항목 하나: 글리프 id, 그것이 나타내는 텍스트,
// 그리고 텍스트 공간에서의 advance와 오프셋
SetLength(Glyphs, 3);
Glyphs[0].GlyphID := 402; Glyphs[0].UnicodeText := 'ffi';
Glyphs[0].Advance := 18.4;
Glyphs[1].GlyphID := 71; Glyphs[1].UnicodeText := 'c';
Glyphs[1].Advance := 9.8;
Glyphs[2].GlyphID := 74; Glyphs[2].UnicodeText := 'e';
Glyphs[2].Advance := 9.2;
Options := TPdfCidFontOptions.Default; // 서브셋 우선
Options.VerifyExtraction := True;
if Pdf.AddCidType2Text(LoadFileBytes('NotoSans.ttf'), Glyphs,
11, 72, 700, Options, Report) then
Writeln(Format('%d glyphs, %d unique, font %d -> %d bytes, match=%s',
[Report.GlyphCount, Report.UniqueGlyphCount,
Report.OriginalFontBytes, Report.EmbeddedFontBytes,
BoolToStr(Report.ExtractionMatches, True)]));
finally
Pdf.Free;
end;
end;
서브셋팅, 그리고 반환 코드만으로는 알 수 없는 검사
실제로 사용된 글리프로만 폰트를 서브셋팅하는 것은 300KB를 임베드하는 것과 12KB를 임베드하는 것의 차이이며, 폰트가 여러 개인 문서에서는 파일을 이메일로 보낼 수 있는지 여부를 좌우합니다. 플랫폼 서브셋팅 경로는 글리프 목록을 직접 받는데, 이는 호출자가 이미 사용 중인 모든 글리프 식별자를 알고 있는 이 API에 완벽하게 들어맞습니다
이것이 주지 않는 것은 확신입니다. 서브셋팅 호출은 성공을 보고하면서도 사용할 수 없는 출력을 반환할 수 있으므로, 컴포넌트는 결과를 받아들이기 전에 세 가지 속성을 검증합니다: 출력이 원본보다 작아야 하고, 읽을 수 있는 maxp 테이블을 가진 유효한 sfnt로 다시 파싱되어야 하며, 요청된 가장 높은 원본 식별자까지의 글리프를 유지해야 합니다. 이 중 하나라도 실패하면 서브셋은 거부됩니다
그다음에 무엇을 할지는 호출자의 정책입니다. 기본값인 pcfemSubsetPreferred 아래에서는 거부된 서브셋이 전체 폰트 임베드로 폴백하므로, 페이지는 올바르지만 조금 더 커질 뿐입니다. pcfemSubsetRequired 아래에서는 페이지가 수정되기 전에 연산이 실패하는데, 이는 크기 제약이 있는 파이프라인이 원하는 동작입니다. pcfemFull은 서브셋팅을 아예 건너뜁니다. 보고서는 SubsetAttempted, SubsetApplied, UsedFullFontFallback, SubsetErrorCode를 통해 어떤 경로를 거쳤는지 알려줍니다
거짓 양성을 만들어낼 수 없는 검증
VerifyExtraction을 켜면 컴포넌트는 자신이 기록한 것을 다시 읽어낼 수 있는지 확인합니다. 이를 하는 순진한 방법은 페이지 전체를 추출해 예상 문자열을 검색하는 것인데, 이는 잘못된 방법입니다: 이미 그 텍스트를 담고 있던 페이지는 새 텍스트가 잘못 기록되었더라도 검사를 통과해 버립니다
대신 텍스트 페이지가 다시 만들어지고, 이 호출로 삽입된 객체 핸들이 삽입 순서대로 개별적으로 읽혀 이어붙여집니다. 그 결과는 예상 텍스트와 비교되며, 보고서는 불리언 값과 함께 두 문자열을 모두 노출하므로, 불일치가 단순히 감지되는 데 그치지 않고 진단될 수 있습니다
개발 중에는, 그리고 추출 가능성이 요구사항인 파이프라인에서는 — 검색 가능한 아카이브, 접근성 준수, 다운스트림 텍스트 마이닝 등 — 이를 켜 두세요. 비용은 호출당 텍스트 페이지 재구성 한 번이며, 그래서 촘촘한 루프에서는 기본적으로 켜져 있지 않습니다
예산과 원자적 실패
폰트 바이트, 글리프 인스턴스, 유니코드 코드 단위는 각각 할당 전에 상한이 정해지며, 폰트 형식, TTC 인덱스, 글리프 식별자 범위, 지오메트리 값은 무언가가 기록되기 전에 검증됩니다. 지오메트리는 유한해야 하는데, 이는 손상된 폰트가 셰이핑 엔진에 NaN advance를 건네주기 전까지는 당연해 보이는 요구사항입니다
실패는 페이지 수준에서 원자적입니다. 폰트 로딩, 객체 기록, 콘텐츠 생성, 추출 검증 중 하나라도 실패하면, 그 호출로 삽입된 모든 객체가 역순으로 제거되고 페이지 콘텐츠가 다시 생성됩니다. 실패한 호출은 절반짜리 텍스트 실행이 남은 상태가 아니라 원래 상태 그대로 페이지를 남겨 둡니다
텍스트 파이프라인에서 이것이 차지하는 위치
역할 분담은 명확히 짚어 둘 가치가 있습니다. 셰이핑 — 문자를 위치가 정해진 글리프로 바꾸는 것 — 은 이 API의 일이 아닙니다. 이는 셰이핑 엔진의 몫이며, 컴포넌트 자체의 양방향 및 복잡한 문자 체계 지원은 이모지, CJK, 서로게이트 쌍 처리에서 다룹니다. 이 API는 셰이퍼가 만들어낸 글리프 실행을 가지고 그 이후에 호출하는 것입니다
문맥에 따른 셰이핑이 없는 평범한 라틴 텍스트에는, 더 단순한 문자열 기반 텍스트 API가 올바른 도구이며 더 작은 코드를 만들어냅니다. 셰이핑된 출력이 있거나, 글리프 식별자를 정확히 제어해야 하거나, 이름으로 해석되는 폰트가 아니라 직접 가지고 있는 바이트로부터 폰트를 임베드해야 할 때 CID Type 2 기록을 선택하세요 — 폰트 해석의 대안은 PDF 폰트 대체 제어하기에서 설명하는 제공자 메커니즘입니다
배포 관련 참고 사항 하나: 폰트를 임베드하는 것은 기술적인 문제만큼이나 라이선스 문제입니다. 폰트마다 임베드가 전혀 허용되지 않거나, 보기용으로만 허용되거나, 편집용으로 허용되는지가 다릅니다. 라이브러리는 주어진 바이트가 무엇이든 임베드할 것이며, 라이선스를 확인하는 것은 파일 형식이 아니라 사용자의 책임입니다
글리프 수준 텍스트 기록, 폰트 프로비저닝, 텍스트 추출은 Delphi, C++Builder, Lazarus에서 동일한 페이지 모델을 공유합니다. 전체 API는 Delphi용 PDFium Component 페이지에서 설명합니다