PDF/A와 PDF/UA는 서로 아무 관련도 없는 두 가지 질문에 답한다. 이 둘을 하나의 접근성-및-아카이브 체크박스로 취급하는 것이야말로 손상된 파일이 컴플라이언스 라벨을 붙인 채 아카이브에 도달하는 방식이다. PDF/A는 파일이 20년 뒤에도 여전히 충실하게 렌더링될지를 묻는다. PDF/UA는 보조 기술이 오늘 이를 읽을 수 있는지를 묻는다. 어떤 문서든 한쪽은 완벽하게 통과하면서 다른 한쪽은 실패할 수 있으므로, 정직한 판정은 오직 둘 다 실행해야만 나오며, 그것도 하위 시스템이 메타데이터에 구워 넣어진 적합성 식별자를 신뢰하기 전에, 파일이 기록되기 전에 실행해야 한다. 그 식별자는 스스로의 선언일 뿐이다. 포맷의 어떤 부분도 그것이 사실이도록 강제하지 않으며, 표준에 대비해 검증하지 않은 채 XMP에 "PDF/A-1b"라고 써 넣는 애플리케이션은 오직 그 라벨만 읽는 모든 소비자에게 준수하는 것처럼 보이는 파일을 만들어 낸다. losLab PDF Library(PDF Library for Delphi)는 두 검증기를 모두 라이브러리 안에 내장함으로써 Delphi와 C++Builder를 위해 이 간극을 메워 준다. 그래서 검사는 별도의 외부 서비스를 세울 필요 없이 프로세스 내부에서 실행된다
정반대의 이유로 파일을 탈락시키는 두 표준
ISO 19005(PDF/A)는 재현 계약이다. 준수하는 파일은 그것을 만들어 낸 시스템을 전혀 본 적 없는 소프트웨어에서도 수십 년 뒤에 동일하게 렌더링되어야 하므로, 규칙은 외부 의존성을 공략한다: 모든 폰트가 임베딩되어야 하고, 색상은 임베딩된 ICC OutputIntent에 고정되거나 디바이스 독립적인 공간으로 표현되어야 하며, PDF/A-1에서는 암호화가 없어야 하고, JavaScript도 없어야 하며, XMP 메타데이터는 문서 정보 딕셔너리와 일치해야 한다. ISO 14289(PDF/UA)는 대신 의미 계약이다. 보조 기술은 문서를 순회하며 의미를 얻어 내야 하는데, 이는 완전히 다른 계층에 존재한다: 완전한 구조 트리, 그림에 대한 대체 텍스트, 화면 표시용으로 설정된 문서 제목, 건너뛰지 않는 제목 수준, 페이지가 화면에서 사라진 뒤에도 살아남는 테이블 헤더 관계 등이다
두 표준이 서로 다른 계층을 감시하기 때문에, 실제로 문제를 일으키는 파일은 그 사이 어딘가에 걸쳐 있는 파일들이다. 아카이브 기준으로는 완벽한 문서가 스크린 리더에게는 침묵할 수 있다. 아름답게 태그된 문서가 10년 뒤에는 존재하지 않을 데스크톱 폰트를 참조할 수도 있다. 공공 부문 출판물은 두 요구 사항이 동시에 걸리는 흔한 지점이며, 그곳의 파이프라인은 이 둘을 하나의 게이트로 뭉뚱그릴 수 없다. 검사 결과는 서로 다른 사람에게 전달되어야 한다. 임베딩되지 않은 폰트는 PDF를 생성하는 코드의 결함이지만, 누락된 대체 텍스트는 콘텐츠 템플릿을 소유한 쪽의 문제이며, 이 둘을 뒤섞은 리포트는 결국 두 번 전달될 뿐이다
PDF/A의 어느 파트를 목표로 삼는지는 그것을 실제로 맞추는지 못지않게 중요하다. PDF/A-1은 PDF 1.4에 고정되어 있으며 투명도와 JPEG2000을 거부하는데, 이 둘은 현대적인 리포트 출력이 별생각 없이 사용하는 기능들이다. PDF/A-2(ISO 19005-2, ISO 32000-1 기반)는 둘 다 허용하며 새로운 아카이브를 위한 합리적인 기본값이다. PDF/A-3은 한 걸음 더 나아가 어떤 유형이든 파일 임베딩을 허용하는데, 이는 규제 대상인 전자 세금계산서 포맷들이 의존하는 기능이다. 2026년에도 여전히 PDF/A-1b를 표준으로 삼고 있는 팀은 대개 누군가 15년 전에 써 둔 요구 사항을 그대로 지고 있는 경우이며, 목표 파트를 재협상하는 편이 시스템이 내보내는 모든 차트에서 투명도를 벗겨 내는 것보다 대개 더 저렴하다
수집 시점의 구조화된 검사 결과
플랫 API 진입점은 CheckFileCompliance이며, 테스트 선택자는 PDF/A가 1, PDF/UA가 2다. 이 함수는 항목마다 한 줄씩 개별 검사 결과를 담은 문자열 목록 핸들을 반환하는데, 이는 자동화된 게이트가 순회하고 싶어 하는 바로 그 형태다:
function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0); // 1 = PDF/A
if ListId = 0 then
begin
// 0은 "검사 결과 없음" 또는 "파일을 읽을 수 없음"을 의미함 -- 통과시키기 전에 구분할 것
Result := Pdf.LastErrorCode = 0;
Exit;
end;
for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
Pdf.ReleaseStringList(ListId);
Result := False;
end;
이것이 무인으로 돌아갈 수 있는지는 두 가지 세부 사항에 달려 있다. 첫째는 정반대의 두 가지를 의미하는 반환값이다. CheckFileCompliance는 파일이 완전히 준수할 때도 0을 반환하고, 파일을 아예 열 수 없었을 때도 0을 반환하는데, 내부적으로 두 경우 모두 빈 결과 목록이 0으로 접히기 때문이다. 0을 통과로 읽는 게이트는 손상된 업로드도 그대로 아카이브로 흘려보내 버리므로, 위의 게이트가 하듯이 그 0을 신뢰하기 전에 LastErrorCode로 구분하라. 둘째는 파일이 생애주기의 어느 지점에 있느냐다. 검사기는 전체 문서 모델 대신 라이브러리의 스트리밍 리더에서 동작하며, 파일을 읽기 공유 모드로 직접 열고 LoadFromFile은 전혀 호출하지 않는데, 이것이 객체 트리를 구축하지 않고도 수 기가바이트짜리 입력을 처리할 수 있는 이유다. 같은 스트리밍 오픈은 다른 프로세스가 여전히 쓰기용으로 파일을 붙잡고 있는 동안에는 실패하며, 진행 중인 업로드가 정확히 그 상태다. 전송이 끝난 뒤에 게이트를 걸어라
이 스트리밍 설계는 부하가 걸릴 때 다시 한번 빛을 발한다. 각 검사는 입력을 읽기 전용으로 열고 읽기용으로 공유하므로, 문서 집합 감사는 워커당 TPDFlib 인스턴스 하나씩으로 워커 스레드나 프로세스 전체에 걸쳐 확장되며 서로 경합하지 않는다. 관리가 필요한 자원은 핸들 그 자체다. CheckFileCompliance가 반환하는 0이 아닌 모든 결과는 ReleaseStringList를 호출할 때까지 계속 할당된 상태로 남으며, 이를 해제하는 것을 잊은 장기 실행 게이트는 죽지 않고 그저 누군가 원인을 찾아 나설 때까지 천천히 메모리를 흘려보낼 뿐이다
사람을 위한 리포트, 빌드 게이트를 위한 diff
검사 결과 목록은 게이트에는 맞는 형태이지만 템플릿 팀에 보내는 이메일에는 맞지 않는 형태다. CreatePreflightReport는 같은 분석을 읽기 쉬운 산문으로 렌더링해 주고, CreatePreflightReportEx는 리포트 포맷 선택자를 추가해 주며, SavePreflightReport는 이를 디스크에 기록해서 리포트가 배포되는 문서 패키지 안에 함께 이동할 수 있게 해 준다. 많은 아카이브 계약이 이 리포트를 단순한 내부 산출물이 아니라 그 자체로 독립적인 산출물로 요구한다
이 계열 중 조용히 제 몫을 하는 함수가 ComparePreflightReports다. 컴플라이언스도 다른 어떤 동작과 마찬가지로 회귀가 발생할 수 있는 영역이다. 템플릿 수정, 새로 라이선스한 회사 폰트, 라이브러리 업그레이드는 각각 지난 릴리스에는 없었던 검사 결과를 새로 만들어 낼 수 있는데, 그 어느 것도 스스로 알려 오지 않는다. 대표성 있는 문서 집합에 대한 기준(golden) 리포트를 버전 관리 아래 보관하고, 변경할 때마다 다시 생성한 다음, ComparePreflightReports를 실행해 차이를 계산하라. 빈 diff는 보관할 가치가 있는 릴리스 산출물이다. 예상치 못한 검사 결과는 빌드를 실패시키는데, 이는 감사 시점보다 훨씬 저렴하게 문제를 발견하는 지점이다
첫 실행에 통과하는 출력물 생성하기
프리플라이트는 다른 곳에서 도착하는 파일에서 제 몫을 한다. 자체 코드가 생성하는 문서라면, 생성 이후에 위반 사항을 찾아 다시 손보는 것은 돌아가는 느린 길이다. PDF Library for Delphi는 각 표준마다 생성 쪽 모드를 갖추고 있으며, 같은 문서에 대해 둘 다 켤 수 있다:
var
Pdf: TPDFlib;
Diag: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1);
Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
Pdf.SetPDFUAMode('en-US');
Pdf.SetInformation(1, 'Quarterly Statement'); // /Title: PDF/UA에 필수
// ... 여기에 태그된 콘텐츠를 그림 ...
Diag := Pdf.GetPDFUADiagnostics;
if Diag <> '' then
Writeln('fix before shipping: ', Diag);
Pdf.SaveToFile('statement.pdf');
// 실제로 의미 있는 프리플라이트는 저장된 파일에 대해 실행됨:
Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
finally
Pdf.Free;
end;
end;
함정은 저장 시점에 숨어 있다. 적합성 관련 수정 중 여러 가지는 모드를 켜는 시점이 아니라 문서가 직렬화되는 동안 일어난다: 주석의 인쇄 플래그를 강제로 켜는 것, PDF/A-3 임베딩 파일을 위한 기본 AFRelationship을 기록하는 것, PDF/UA를 위해 탭 순서와 양식 필드 설명을 정규화하는 것 등이다. 메모리에 있는 문서는 디스크에 안착하는 문서와 바이트 단위로 동일하지 않으므로, 의미 있는 유일한 프리플라이트 판정은 저장된 파일로부터 계산한 것뿐이다. statement.pdf 자체를 검증하라. 메모리에 여전히 남아 있는 객체로부터 준수 여부를 추론하지 말라. 당신이 판정하려는 바이트가 실제로 배포한 바이트가 아니기 때문이다
시각적 문서와 함께 기계 판독 가능한 XML을 담고 있는 인보이스 시나리오는 PDF/A-3 위에 구축된 ZUGFeRD와 Factur-X 패턴을 따른다. 이런 경우는 SetPDFA3DefaultAFRelationship로 첨부 관계를 명시적으로 설정해야 하는데, ISO 19005-3이 모든 임베딩 파일에 대해 문서와의 관계 역할을 선언하도록 요구하기 때문이다. 이를 설정하지 않은 채로 두면 임베딩된 XML은 명시된 목적이 없는 그저 하나의 블롭이 되며, 검증기는 이를 알아챈다
독립적인 심판: veraPDF와 Acrobat
생성자가 자기 출력물의 유일한 심판이 되어서는 안 된다. PDF Library for Delphi의 검사기들은 프로세스 내부에서 빠르고 구조화된 판정을 내려 주는데, 이는 핫 패스에서 원하는 바로 그것이지만, 아카이브 배치를 위한 릴리스 게이트는 여전히 팀 내부에서 만들지 않은 검증기를 통과시켜야 한다. veraPDF는 PDF/A를 위한 커뮤니티가 유지하는 레퍼런스 구현체이자 대부분의 아카이브가 승인 기준에서 지목하는 도구이므로, 맞춰야 할 대상은 바로 이것이다. veraPDF와 프로세스 내부 검사가 서로 다른 결론을 낼 때는 Acrobat의 프리플라이트 프로필이 유용한 결정타가 되어 준다. 저장하는 모든 리포트 옆에 검증기 이름과 버전을 함께 기록해 두라. 파일이 veraPDF를 통과했다는 주장은, 그 도구가 릴리스마다 규칙을 더 엄격하게 만들기 때문에 통과시킨 빌드 번호 없이는 별 의미가 없다
검증기들은 표준의 경계 지점에서 실제로 서로 다른 판정을 내리며, 그럴 때 답은 자신이 선호하는 도구를 고르는 것이 아니다. 여전히 그 불일치를 유발하는 최소한의 샘플로 파일을 줄이고, 이를 표준 원문과 대조해서 읽어 보라. 그렇게 한 시간을 들이면 보통 둘 중 하나가 드러난다: 상류 프로젝트에 제보할 가치가 있는 진짜 도구 버그이거나, 팀이 잘못 읽어 왔던 조항인데 다음 사람이 같은 논쟁을 다시 벌이지 않도록 컴플라이언스 노트에 적어 두어야 할 것이다
암호화된 입력에는 지름길이 있다. 두 검사기 모두 비밀번호 인자를 받지만, 암호화 딕셔너리를 가진 PDF/A-1 파일은 이미 비준수 상태다. ISO 19005-1이 암호화를 아예 금지하기 때문이며, 그래서 암호화된 제출물은 더 깊은 분석이 실행되기 전에 돌려보낼 수 있다. 암호화 딕셔너리가 실제로 무엇을 허용하는지 알아내는 것은 그 자체로 별개의 작업이며, PDF 암호화 및 권한 감사에서 다룬다
PDF/UA 검사 결과는 거의 항상 애초에 구조 트리가 어떻게 작성되었는지로 거슬러 올라가며, 그 배경이 되는 태깅 기법은 Delphi에서 태그 PDF 구조 트리 만들기에서 다룬다. 디지털 서명도 함께 요구하는 아카이브라면 이 게이트를 PAdES 서명 및 검증의 워크플로와 짝지어야 한다. 전체 프리플라이트 API 레퍼런스는 losLab PDF Library for Delphi 제품 페이지에 있다