같은 필드를 담고 있으면서도 전혀 다르게 동작하는 두 종류의 양식이 있다. AcroForm은 필드를 실제 페이지 콘텐츠 위에 놓인 평범한 PDF 객체로 유지하므로, 표준을 준수하는 어떤 리더든 이를 그려 낼 수 있다. 동적 XFA 양식은 PDF로는 거의 아무것도 유지하지 않는다: 필드, 레이아웃, 심지어 페이지 형태까지 XML 패키지 안에 담겨 있으며, 눈에 보이는 페이지는 오직 Adobe만이 널리 배포한 레이아웃 엔진이 여는 시점에 생성해 낸다. 그 파일을 웹 뷰어, 아카이브 렌더러, 텍스트 추출기에 넣으면 양식을 얻지 못한다. 대신 "Please wait... If this message is not eventually replaced by the proper contents of the document, your PDF viewer may not be able to display this type of document."라고 적힌 회색 페이지 하나만 얻게 된다. 정부나 보험 관련 서류를 다뤄 본 사람이라면 누구나 이 페이지를 보자마자 알아본다
이 플레이스홀더는 손상이 아니다. XFA 프로세서가 없을 때 정확히 포맷 명세가 규정한 대로 일어나는 일이며, 2026년 현재 이는 데스크톱 Acrobat을 제외한 거의 모든 뷰어에 해당하는 이야기다. 그러므로 실무적인 선택은 동적 양식이 다운스트림의 무언가에 도달하기 전에 평범한 AcroForm으로 변환해 두는 것이다. Delphi와 C++Builder를 위한 losLab의 PDF 라이브러리인 HotPDF는 이 변환을 코드로 수행하며, XML 양식을 네이티브 페이지 위의 네이티브 필드로 재구성한다
왜 이 두 모델은 공존할 수 없는가
AcroForm은 ISO 32000-1 §12.7에 정의되어 있다. 각 필드는 위젯 주석과 외관 스트림을 가진 PDF 객체이고, 페이지는 진짜 PDF 콘텐츠이며, 데이터는 그 위에 얹힌다. XFA는 이를 뒤집는다: 양식은 AcroForm 딕셔너리의 /XFA 항목에 저장된 XDP 패키지, 즉 하나의 XML 문서이며, 동적 양식의 PDF 페이지는 "Please wait" 플레이스홀더 외에는 아무것도 담고 있지 않은데, 실제 콘텐츠가 애초에 PDF로 직렬화된 적이 없기 때문이다. 리더는 파일을 둘 중 하나의 모델로만 처리한다. /XFA 항목을 무시하면 텅 빈 껍데기가 보이고, XFA 엔진 없이 이를 존중하면 경고 화면이 보인다. ISO 32000-2는 PDF 2.0에서 XFA를 아예 제외해 버림으로써 이 논쟁을 끝냈으며, 이것이 "아직 할 수 있을 때 변환하라"가 예외적인 상황에서 일상적인 수집 정책으로 바뀐 주된 이유다
무엇이든 변환하기 전에 먼저 분류하라. 모든 XFA 파일이 플레이스홀더를 보여 주는 것은 아니기 때문이다. 정적 XFA 양식은 XML 옆에 미리 렌더링된 PDF 페이지를 함께 담고 있어서 어디서든 표시되며, 값을 채울 때만 문제를 일으킨다. 동적 양식은 플레이스홀더만 담고 있어서 변환하기 전까지는 쓸 수 없다. 신뢰해야 할 것은 확장자나 보낸 사람이 아니라 문서 그 자체다. Adobe가 아닌 뷰어에서도 실제 콘텐츠를 렌더링하면서 여전히 /XFA 항목을 갖고 있는 파일은 정적이거나 하이브리드이며, 경고 화면을 보여 주는 파일은 동적이다. 수집된 각 파일이 어느 쪽에 해당하는지 기록해 두라. 이 두 종류는 나중에 서로 다른 방식으로 문제를 일으키는데, 수집 로그에 이미 "dynamic XFA, converted, 47 fields mapped, 2 warnings"라고 적혀 있다면 아카이브된 양식이 비어 있다는 문의는 몇 초 만에 해결된다
로드된 XFA 문서를 네이티브 필드로 변환하기
변환은 이미 메모리에 있는 문서를 대상으로 실행된다. FlattenLoadedXFA는 XFA 템플릿과 그 데이터 패킷을 파싱하고, 양식을 레이아웃한 다음, 이를 실제 PDF 페이지 위의 AcroForm 필드로 재구성한다:
var
Pdf: THotPDF;
MappedCount, I: Integer;
Warnings: TStrings;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('dynamic_xfa.pdf');
MappedCount := Pdf.FlattenLoadedXFA(True); // True = 필드가 계속 편집 가능한 상태로 남음
Warnings := Pdf.XFAFlattenWarnings;
for I := 0 to Warnings.Count - 1 do
Log('XFA flatten warning: ' + Warnings[I]); // 매핑되지 않은 요소들
Pdf.SaveLoadedDocument('native_acroform.pdf');
Log(Format('Mapped %d fields', [MappedCount]));
finally
Pdf.Free;
end;
end;
반환값과 경고 목록은 디버그용 잡음이 아니라 출력물이므로 둘 다 보관하라. 변환은 그 본질상 정보를 잃는다: XFA 스크립팅, 계산 필드, 동적 서브폼 동작에는 AcroForm 대응물이 없으며, XFAFlattenWarnings는 매핑되지 않은 모든 템플릿 요소를 이름으로 알려 준다. 경고 목록 없이 변환된 파일을 아카이브해 두면, 언젠가 아카이브된 사본에서 빈 합계 상자를 마주하고도 왜 그런지 기록이 전혀 없는 상황에 처하게 된다. Editable 플래그는 새 필드가 계속 채울 수 있는 상태로 남을지를 제어한다. 사람들이 이후에도 양식을 계속 사용한다면 True를 넘기고, 목표가 고정된 기록이라면 값을 잠가 두라
변환 검증은 일부는 시각적이고 일부는 구조적이며, 둘 다 필요하다. 구조적인 절반은 쉽다: 필드 개수가 MappedCount와 일치하는지만 확인하면 된다. 실제 손상을 잡아내는 쪽은 시각적인 절반이다. 여전히 XFA 엔진을 실행하는 유일한 뷰어인 데스크톱 Acrobat에서 원본 양식을 열고, 그 옆에 일반 리더로 변환된 파일을 열어, 템플릿마다 채워진 샘플을 최소 하나씩 값과 레이아웃을 비교하라. XFA 엔진이 2026-06-11로 표시한 날짜가 AcroForm 사본에서는 서식 없는 원시 값으로 나타날 수 있는데, 이는 오직 직접 눈으로 봐야만 잡아낼 수 있다
입력이 XDP 패키지일 때
모든 작업이 값이 채워진 PDF에서 시작하는 것은 아니다. 때로는 양식 설계 도구에서 내보내지거나 파트너 시스템에서 넘겨받은 XDP 패키지 자체만 받는 경우도 있다. ApplyXFAAsAcroForm은 로드 단계를 건너뛰고 패키지를 현재 문서에 곧바로 적용한다:
XDPBytes := TFile.ReadAllBytes('benefit-claim.xdp');
MappedCount := Pdf.ApplyXFAAsAcroForm(XDPBytes, True);
같은 호출 그룹은 반대 방향으로도 동작하는데, XFA를 소비하는 대신 만들어 내야 하는 더 드문 경우를 위한 것이다. AddXFAPacket은 'xdp'나 'config' 같은 개별 이름의 패킷을 붙인다. SetXFADocument는 완전한 단일 스트림 페이로드를 한 번의 호출로 설치한다. ClearXFAPackets는 등록 내용을 지워 처음부터 다시 시작할 수 있게 해 주며, AddXFASignaturePacket은 XML 양식 데이터에 직접 서명하는 워크플로를 위해 XAdES 자료를 임베딩한다. 2026년에 XFA를 생성해야 하는 경우는 틈새 요구인데, 거의 항상 다른 무엇도 받아들이지 않는 레거시 소비자 하나 때문에 강제되는 경우다. 하지만 계약에서 이를 명시할 때는 이 호출들 덕분에 별도의 도구 없이도 설정 하나만 선택하면 끝나는 일이 된다
"플래튼(flatten)"의 또 다른 의미
"플래튼(flatten)"이라는 단어는 많은 대화를 헷갈리게 만드는데, 이 단어가 완전히 다른 두 번째 작업, 즉 상호작용하는 객체가 하나도 남지 않을 때까지 AcroForm 필드 외관을 페이지 콘텐츠 스트림에 구워 넣는 작업을 가리키기 때문이다. HotPDF에는 오늘 기준으로 이를 위한 API가 없으며, 이 사실은 프로젝트 중간이 아니라 지금 알아 두는 편이 낫다. 라이브러리가 대신 제공하는 것은 필드가 만들어질 때 필드 수준에서 잠그는 방법으로, 문서 권한이 이를 뒷받침한다:
// 필드 생성 시점에 값을 잠금: 읽기 전용 텍스트 필드
Pdf.CurrentPage.AddTextField('CaseNumber', 'BC-2026-0117',
Rect(50, 700, 220, 720), 0, [ffReadOnly]);
// 이중 안전장치: 문서 전체에서 양식 채우기를 제한함
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.OwnerPassword := 'records-owner';
Pdf.ProtectOptions := [prPrint, prInformationCopy, prExtractContent];
// 채우기 권한을 부여하지 않음: 집합에 prFillAnnotations가 빠져 있음
이 방법이 무엇을 해 주고 무엇을 해 주지 않는지 분명히 해 두자. 읽기 전용 필드는 여전히 양식 객체다. 뷰어의 필드 패널에도 나타나고, 그 값은 양식 API를 통해 읽을 수 있으며, 파일을 다시 쓰는 도구라면 읽기 전용 플래그를 다시 지울 수도 있다. 권한 플래그는 문턱을 높여 주지만 뷰어가 이를 존중하기로 선택하는지에 달려 있는데, 이는 ISO 32000-1이 명확히 밝히고 있는 한계다. 규제 기관이 아카이브된 기록에 양식 객체가 아예 존재해서는 안 된다고 요구한다면, 오늘날 HotPDF로 할 수 있는 정직한 답은 문서를 재구성하는 것이다: 값을 읽어 낸 다음, 읽기 전용 플래그를 플래튼으로 포장하는 대신 새 페이지에 평범한 TextOut 콘텐츠로 그려 넣는 방식이다. 권한 경로에서 기억해 둘 한 가지는 CryptKeyLength는 BeginDoc 이전에 설정해야 한다는 것이다; 나머지는 AES-256 암호화 및 권한에 관한 글에서 다룬다
아카이브 컴플라이언스에서 XFA가 의미하는 것
PDF/A와 PDF/X는 둘 다 XFA를 아예 거부한다. 따라서 ISO 19005 아카이브로 들어가는 파이프라인은 먼저 변환해야 하며, 그 순서는 타협할 수 없다: 로드, FlattenLoadedXFA, 저장, 그런 다음 AcroForm 결과물에 대해 아카이브 생성이나 검증을 실행하는 순서다. 변환을 컴플라이언스의 증거로 취급하지 말라. 변환은 양식 모델만 고칠 뿐 폰트, 색상, 메타데이터는 원래 상태 그대로 남기므로, 신뢰하기 전에 veraPDF로 출력물을 검증하라. 양식이 AcroForm 쪽으로 넘어오고 나면, 그 동작은 별도의 제어 체계를 갖게 된다. JavaScript 트리거, 제출 액션, 검증 스크립트는 HotPDF AcroForm 필드 및 액션에 관한 글에서 다룬다
여기서 다룬 XFA 등록, 변환, 양식 API는 Delphi 및 C++Builder용 HotPDF Delphi Component에 포함되어 있으며, 그 문서는 최근 릴리스에 걸쳐 성장해 온 XFA 기능 집합을 계속 추적하고 있다