기술 문서

PDFium을 사용한 Delphi에서의 XFA 서식 탐지 및 XML 데이터 추출

예 — PDFium은 XFA 서식을 완벽히 지원하지만, 여기에는 한 가지 매우 중요한 개념적 격차(Split)가 있습니다. Delphi 및 Lazarus용 PDFium 컴포넌트는 표준 DLL 환경에서도 아무런 런타임 추가 의존성 없이 XFA 서식을 탐지하고 내부의 template, datasets, config 패킷을 순수 XML 데이터로 추출해 낼 수 있습니다. 그러나 동적(Dynamic) XFA 서식을 구동하여 XML 데이터 명세를 모니터 상의 상호작용 가능한 활성 페이지로 직접 렌더링하는 작업은 완전히 다른 차원의 과제이며, 반드시 V8 자바스크립트 엔진이 보강된 전용 DLL이 요구됩니다. 개발자 커뮤니티에서는 흔히 PDFium이 XFA를 지원하는지 여부를 단순한 예/아니오의 단일 기능처럼 질문하곤 하지만, 실질적인 진실은 XFA 데이터를 읽어 들이는 행위와 XFA 서식을 직접 렌더링 화면에 띄우는 행위는 완전히 상이한 연산이며 의존성 구조도 180도 다르다는 점입니다

이 가이드는 Delphi 및 C++Builder용 네이티브 VCL 래퍼인 PDFium 컴포넌트를 사용하여 두 가지 영역 모두를 해결하는 법을 제시합니다. 우선 첫 단계로 XFA 서식 탐지 및 정적 데이터 추출 경로(FormType 프로퍼티, XFA 속성 및 GetXfaPacket* 판독 기능)를 살펴보고, 이어서 V8 엔진 환경 하의 동적 런타임 제어 사양을 분석한 뒤, 배포된 DLL이 동적 렌더링 엔진을 구동하지 못할 때 예외 처리를 유연하게 수행해 주는 기능 체크용 프로브(Probe) 시스템을 안내합니다

PDFium이 XFA 서식을 지원하나요?

이 기능을 단일 스펙이 아닌 완전히 별개의 두 가지 기술적 역량으로 해석해야 오독이 없습니다. 첫째, 정적 XFA 데이터 추출은 항상 활성화되어 있습니다: GetXfaPacketCount, GetXfaPacketNameGetXfaPacketContent를 제어하는 저수준 FPDF_GetXFAPacket* 익스포트 함수들은 설령 XFA 빌드 옵션이 비활성화된 pdfium.dll 바이너리라 할지라도 최근에 빌드된 모든 표준 라이브러리 상에 기본 탑재되어 제공됩니다. 이것이 서식으로부터 template, datasets, config XML 데이터를 아무런 추가 모듈 배포 없이 곧바로 추출해 낼 수 있는 물리적 이유입니다. 엔진의 실개입이 없는 순수 구조적 텍스트 리딩이기 때문입니다. 둘째, 동적 XFA 구동은 실시간으로 서식을 동작시켜 수식 스크립트를 계산하고 XFA 레이아웃 엔진으로 페이지 배치 연산을 처리하는 완전히 격리된 별개 기능입니다. 이 기능은 V8 자바스크립트 엔진과 연동하여 XFA 기능이 온전히 켜진 채로 정교하게 컴파일된 전용 PDFium 빌드에서만 제한적으로 작동하며, 이것이 다수의 토론 포럼에서 논란을 유발하는 지점입니다

PDF 표준 규격은 이 격차를 아주 명쾌하게 규정합니다. XFA는 핵심 PDF 문법 외에 Adobe XFA 3.3(2012) 규격서에서 별도로 정의되며, PDF 1.7 §8.6.7 명세는 PDF 파일 내에서 XFA 서식이 운송되는 물리 구조를 가이드합니다: 서식 패킷들은 AcroForm 딕셔너리의 /XFA 엔트리 아래에 저장되며, 열릴 때 반드시 XFA 엔진에 의해 가공 레이아웃 처리를 먼저 거쳐야 하는 동적 문서는 카탈로그 정보 내에 /NeedsRendering true 플래그를 할당합니다. 이 두 가지 핵심 식별 기호가 PDFium 컴포넌트가 탐지하는 실체이며, 개발자가 미지의 입력 파일을 자동 분석할 때 신뢰할 수 있는 가장 확실한 기준입니다

XFA와 일반 AcroForm의 구조적 차이점

AcroForm은 전통적인 PDF 대화형 서식 모델입니다: 텍스트 상자, 체크 박스, 라디오 그룹 등 상호작용 위젯(Widget annotation)이 일반 PDF 페이지 상의 고정된 좌표에 영구 배치되어 있습니다. 모니터로 목격하는 페이지 껍데기가 물리적으로 파일 내에 영구 고정되어 실재하며, 입력 필드가 그 위에 스티커처럼 얹혀 있는 구조입니다. 반면 XFA는 정반대의 메커니즘을 취합니다. 상호작용 템플릿과 정산 로직이 완전히 XML 코드로만 선언되어 있고, 동적 XFA 파일 내의 실제 PDF 물리 페이지는 대개 빈 껍데기 플레이스홀더에 가깝습니다. 실 데이터 화면은 사용자가 문서를 여는 찰나에 XFA 렌더링 엔진이 구동되어 XML 템플릿을 읽고 데이터 분량에 맞춰 실시간으로 행을 늘리거나 페이지를 연산하여 동적 배치를 끝마친 뒤 비로소 생성됩니다. /NeedsRendering true가 시사하는 바가 바로 이것입니다: 정상적인 XFA 엔진이 개입하지 못하면, 화면에는 내용이 전혀 없거나 단지 '호환 리더기로 다시 열어 달라'는 임시 에러 고지 문구 페이지만 노출되게 됩니다

XFA 데이터 블록 자체는 명칭이 부여된 여러 개의 서식 패킷 집합체이며, 대부분의 실무 정산 작업에서는 오직 3가지 종류의 패킷만 파악하면 됩니다. 서식 입력 양식, 계산 공식, 유효성 검증 규칙과 이벤트 스크립트가 정의되어 있는 template 패킷; 사용자가 기입해 채워 넣은 실제 필드 수치들이 XML 트리 구조로 온전히 기록되어 있는 datasets 패킷; 그리고 XFA 가공 엔진의 처리 매개변수들이 담긴 config 패킷입니다. 이 외에도 localeSet, connectionSet, xdp 래퍼 등이 존재하지만, 서식을 파싱하거나 다른 포맷으로 입력 수치들을 마이그레이션(Migration)하는 대다수의 백엔드 연산에서는 templatedatasets 패킷 두 개만 추출해 내면 작업이 완성됩니다. 이 정보는 단지 스트림(Stream) 내에 격리 기입된 텍스트 XML 데이터에 지나지 않으므로, 데이터 파싱 시 어떤 수식이나 컴파일 코드도 실행할 필요가 없고 이 덕분에 정적 데이터 추출 기능이 외부 엔진에 대한 무의존성 구조를 취할 수 있습니다

Delphi에서 XFA 서식 자동 탐지 구현하기

서식 탐지 연산은 PDFium 컴포넌트의 통상적인 초기화 과정으로 출발합니다: FileName을 입력하고 Active := True를 지정한 뒤 파일이 정상 기동되었는지 검증합니다. Active := True는 예외 크래시를 직접 발생시키지 않으므로(파일 부재, 패스워드 장벽, DLL 유실 시 조용히 Active 상태가 False로 복귀), 사전 이중 검문은 필수적입니다. 파일이 정상적으로 오픈되면 FormType 속성을 통해 해당 문서가 어떤 서식 패러다임을 추종하고 있는지 알아낼 수 있습니다. TPdfFormType이 반환하는 값 중 두 가지가 XFA와 직결됩니다: 정밀 실시간 동적 렌더링이 필수적인 ftXfaFull(Full dynamic XFA)과, 하단에 고정된 아크로폼 페이지를 깔아 두고 상단 레이어에만 XFA 데이터를 오버레이하여 XFA 엔진이 꺼진 환경에서도 최소한의 레이아웃 판독이 보장되는 ftXfaForeground(XFAF)입니다. 또한 XFA 불리언 속성은 두 유형 중 하나라도 만족하는지를 판별해 주는 편리한 단축 속성입니다. 아크로폼 또는 XFAF 문서 상에서 개별 입력 필드 개체들을 필드별로 내비게이션하며 탐색하는 구체적인 실무 코드는 PDFium 폼 필드 탐색 및 내비게이션 기술 가이드를 참고하십시오

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'application-form.pdf';
    Pdf.Active := True;
    if not Pdf.Active then
      Exit;                        // 손상되었거나 암호화되었거나 DLL이 유실된 경우
    case Pdf.FormType of
      ftXfaFull:
        Log('동적(Full dynamic) XFA 서식');
      ftXfaForeground:
        Log('XFAF: 정적 AcroForm 페이지 상에 오버레이된 XFA 레이어');
      ftAcroForm:
        Log('클래식 AcroForm');
    else
      Log('대화형 서식 없음');
    end;
    if Pdf.XFA then
      Log('문서가 내부 XFA 패킷 페이로드를 함유하고 있습니다');
  finally
    Pdf.Active := False;
    Pdf.Free;
  end;
end;

XFA 패킷을 순수 XML 데이터로 추출해 내기

패킷 추출은 외부 추가 모듈을 전혀 수반하지 않으면서 정산상 매우 고가치 정보를 확보할 수 있는 알짜 영역입니다. 우선 로드된 DLL에 관련 기능들이 온전히 해제되어 있는지 진증해 주는 XfaStaticReadable 프로퍼티로 호출 안전장치를 세우고, 이어서 패킷 인덱스를 루프로 순회 처리합니다. GetXfaPacketCount를 통해 확보된 패킷 수만큼 루프를 회전시키며, GetXfaPacketName으로 각 패킷의 영문 스키마 키 값을 확인하고, GetXfaPacketContent로 원본 바이트 배열을 추출합니다(일반적인 텍스트 패킷의 경우 UTF-8 XML 데이터 형태입니다). 만약 조준 추출하고자 하는 패킷 키가 명확하다면 편리하게 GetXfaTemplate, GetXfaDatasets, GetXfaConfig 명칭 도우미 메서드를 기동하여 곧바로 TBytes 형태의 결과물을 획득할 수 있습니다. 입력 정보 정산 파이프라인 구축 시 핵심이 되는 메서드는 GetXfaDatasets입니다: 고객이 입력한 정산 수치가 정형 XML 구조로 고스란히 담겨 있어, PDF 텍스트 정보 추출 가이드에서 안내하는 텍스트 추출 작업처럼 백엔드 데이터베이스로 직접 매핑 및 정산 연동하기가 매우 단순해집니다

procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
  I, Count: Integer;
  PacketName: string;
  Content, Datasets: TBytes;
begin
  if not Pdf.XfaStaticReadable then
    Exit;                          // 패킷 리더 익스포트 함수가 DLL 내에 누락된 경우
  Count := Pdf.GetXfaPacketCount;
  for I := 0 to Count - 1 do
  begin
    PacketName := Pdf.GetXfaPacketName(I);
    Content := Pdf.GetXfaPacketContent(I);   // 원본 UTF-8 XML 데이터
    TFile.WriteAllBytes(
      TPath.Combine(Dir, PacketName + '.xml'), Content);
  end;

  // 또는 명칭 헬퍼를 통해 입력 필드 수치 데이터로 직접 접근합니다:
  Datasets := Pdf.GetXfaDatasets;
  if Length(Datasets) > 0 then
    ParseAndMap(Datasets);
end;

동적 XFA 렌더링에 왜 V8 엔진 배포가 필수적인가요?

결론부터 밝히자면, 동적 XFA 서식은 정적인 그림 문서가 아니라 스스로 기동되는 일종의 소형 소프트웨어 프로그램이기 때문입니다. 템플릿 패킷 내에는 입력 조건에 맞춰 분기를 틀어 주는 FormCalc 스크립트나 자바스크립트 연산 규칙, 입력값 유효성 검증 조건, 그리고 이벤트 리스너 스크립트가 내장되어 있어, XFA 레이아웃 제어 엔진이 이를 실시간 해석해 구동해야만 비로소 페이지 형태와 크기가 물리적으로 획정됩니다. PDFium은 Google의 V8 자바스크립트 엔진과 XFA 연동 스펙이 켜진 채로 정교하게 컴파일된 전용 PDFium 빌드에서만 이 런타임 제어기를 탑재하여 제공하며, V8 탑재형 DLL(pdfium32v8.dll 또는 pdfium64v8.dll)의 크기가 통상적인 표준 DLL보다 압도적으로 비대해지는 이유가 바로 이 내장 엔진 때문입니다. V8 엔진이 없는 간이 배포 환경에서는 이 스크립트들을 번역하여 계산해 낼 번역가가 누락되어 있으므로, 렌더링용 이미지가 생성되지 못하고 빈 흰색 화면에 갇히게 되며, 앞서 소개한 정적 XML 데이터만 판독하는 수준으로 기능이 제한됩니다

이 V8 전용 빌드를 로드하는 연산은 프로그램 설계 구조상 우회하기 힘든 한 가지 엄격한 제약 사항을 가집니다: 바로 개별 프로세스(Process) 기동 주기를 통틀어 단 한 번만 사전 결정이 가능하다는 점입니다. 단일 프로세스 메모리 영역 내에 일반 pdfium.dll과 V8 지원형 DLL을 동시 병렬 적재하는 구조는 불가능합니다. 두 DLL의 외부 API 진입점 명칭이 물리적으로 동일할 뿐더러 V8 엔진이 전역적인 격리 상태(Isolate state)를 공유하여 충돌하기 때문입니다. 이에 따라 PDFium 컴포넌트는 최초로 DLL이 디스크로부터 메모리에 바인딩되기 전에 V8 구동 여부를 미리 결정지어야 합니다. 이를 자동화하기 위해 LoadDocument는 문서를 불러오기 전 파일 서두의 /XFA/NeedsRendering true 기호를 고속 사전 스캔(Peek)하여(독립적으로 노출된 PdfFileLooksXfa 헬퍼 함수와 동일한 연산), XFA 문서로 확인되는 즉시 DLL 적재 전에 전역 EnableV8Engine := True 플래그를 선제 조율합니다. 만약 일반 덤프용 pdfium.dll이 이미 메모리에 고착되어 선적재 완료된 상태라면 이 플래그 갱신은 무력화되며, 엔진은 런타임 누락 분기 예외로 제어권을 넘기게 됩니다

begin
  // PDFium이 최초 로드되기 전에 XFA 구조를 사전 스캔하여,
  // V8 지원 바이너리가 기동될 수 있도록 조율합니다. PdfFileLooksXfa는
  // 파일을 완벽히 파싱하지 않고도 마크 기호들만 고속 체크해 냅니다.
  if PdfFileLooksXfa('dynamic-form.pdf') then
    EnableV8Engine := True;

  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'dynamic-form.pdf';
    Pdf.Active := True;
    if Pdf.Active and Pdf.XFA then
    begin
      if Pdf.XfaRuntimeAvailable then
        RenderXfaPages(Pdf)          // 엔진 활성화됨: 동적 레이아웃 구동
      else
        ExtractXfaData(Pdf, 'C:\out'); // 정적 패킷 추출 모드로 우회 처리
    end;
  finally
    Pdf.Active := False;
    Pdf.Free;
  end;
end;

XFA 엔진 미배포 환경에서 우아한 대체 예외 처리 수립하기

개발자의 코드가 임의의 가정에 의존하여 오동작하는 참사를 차단하기 위해, 현재 런타임의 XFA 연산 한계를 정밀 진단해 주는 3가지 체크용 프로브(Probe) 시스템을 제공합니다. XfaFeaturesAvailable은 현재 메모리에 올라온 DLL이 XFA/V8 관련 기능들의 심볼 주소를 제대로 갖추고 있는지 전역 검사합니다. XfaStaticReadable은 개별 문서 단위로 작동하며 패킷 리더 함수 진입점이 안정적으로 매핑되어 데이터 정적 추출이 가능한지 보증합니다. 가장 강력한 식별자인 XfaRuntimeAvailable은 XFA 서식 탐지, V8 바이너리 연동 기동, 그리고 내부 FPDF_LoadXFA API 바인딩 연산이 최종 성공한 경우에만 참(True)으로 설정됩니다. 배포 폴더에 XFA 기능이 누락된 구버전 DLL이 배치되어 있거나 또는 이미 일반 DLL이 로드 고정되어 XFA 렌더러가 기동되지 못한 상태라면, 문서가 분명 XFA = True 임에도 불구하고 XfaRuntimeAvailable = False로 설정될 수 있습니다

XFA 문서를 열었으나 V8 가공 렌더러가 미기동되는 상황을 만날 때, PDFium 컴포넌트는 무리한 렌더링 호출 도박을 실행하지 않습니다. 오동작을 강행하는 대신 안전하게 해당 서식을 강제 정적 모드로 락다운(Static mode lockdown) 처리하며, 호스트 프로그램이 조기에 대응할 수 있도록 OnXfaRuntimeMissing 이벤트를 즉각 1회 유발합니다. 개발자는 이 이벤트를 캐치하여 'V8 탑재 빌드 배치 파일 교체가 필요하다'는 알림을 최종 고객 화면에 안내하는 동시에 뒷단에서는 정적 XML 데이터를 안전하게 계속 조회 및 이관하는 우아한 예외 복구(Fallback) 구조를 세울 수 있습니다. 이 이벤트를 적절히 연동해 주는 것만으로도 화면에 아무 경고도 없이 텅 빈 흰색 껍데기만 덩그러니 출력되는 최악의 사용자 경험을 방어해 낼 수 있습니다

procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
  // XFA 문서를 열었으나 로컬 엔진이 구동되지 않을 때 1회 호출됩니다:
  // 일반 DLL이 이미 선로드되었거나 배포된 바이너리에 XFA/V8 기능이 유실된 상태입니다.
  ShowMessage('이 문서는 동적 XFA 서식입니다. 온전한 화면 렌더링을 위해서는 ' +
    'V8 지원 빌드로 실행해야 합니다. 다만 패킷 텍스트 데이터 추출은 계속 지원됩니다.');
end;

비즈니스 소프트웨어를 개발할 때도 이 기능적 임계선을 고객 시나리오에 솔직하게 적용하십시오. 프로그램 배포 시 무겁고 대형인 V8 빌드 DLL을 굳이 동봉 배포하지 않더라도, 동적 화면 출력만 제한될 뿐 XFA 패킷 탐지 및 제출 데이터의 정적 XML 정산 마이그레이션은 완벽하게 수행할 수 있습니다. 이는 복잡한 정부 기관이나 은행 대출 신청 서식 자체를 프로그램 창에 직접 띄워 편집하게 해줄 목적이 아닌, 기제출된 서식 파일로부터 전산 필드 값을 대량으로 정산 수집할 목적의 비즈니스 파이프라인에서 라이선스 및 배포 관리를 효율화하는 최상의 설계가 됩니다. 가벼운 배포 환경을 지향한다면 일반 DLL 배포 상태를 디폴트로 잡고 프로브(Probe) 체크 기능을 통해 각 파일을 처리 가능한 정적 경로로 분기 유도하며, 대화형 서식 입력 편집 기능이 필수가 되는 뷰어 지향 응용단에만 무거운 V8 빌드를 한정 탑재하십시오. 참고로 첨부 파일 정산 업무를 다루는 시나리오라면, 이 정적 접근 사상이 고스란히 반영되어 설계된 Delphi 환경에서의 PDF 첨부 파일 및 임베디드 파일 처리 가이드 역시 도움이 될 것입니다

여기에 소개된 FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig 및 체크 프로브 API들은 모두 Delphi 및 C++Builder용 PDFium 컴포넌트 패키지에 기본 탑재되어 배포됩니다