기술 문서

PDFlibPas로 만드는 Delphi PDF/E-1 엔지니어링 문서

PDF/E-1은 엔지니어링 문서용 아카이브 프로파일이고, PDFlibPas는 SetPDFEMode로 켜는 author 모드와 콘텐츠 스트림을 연산자 단위로 읽어 내려가는 상한 있는 프리플라이트로 이를 구현합니다. 이 프로파일은 라벨만 다른 PDF/A가 아닙니다. 자기 고유의 식별 네임스페이스와 자기 고유의 라이프사이클 메타데이터 요건을 갖고, 여러분이 만나 본 어떤 아카이브 프로파일보다 콘텐츠 검증을 엄격하게 만드는 규칙 하나를 더 갖고 있습니다

엔지니어링 인도물이 이 프로파일이 존재하는 이유입니다. 20년 뒤에도 읽을 수 있고 변경되지 않았음을 증명할 수 있어야 하는 도면 세트. 살아남아야 하는 개정 이력. 다른 건물에 있는 플로터에서도 같은 의미를 유지해야 하는 색. 이 요구들이 만들어 내는 스펙의 요구사항은 대부분 페이지 콘텐츠 바깥, 메타데이터와 색 관리에 자리 잡습니다. 범용 PDF 라이터가 정확히 여기서 틀리는 부분이기도 합니다

PDF/A의 변형이 아니라 자기 고유의 식별

먼저 짚어야 할 것은 PDF/E-1 식별이 PDF/A나 PDF/X 패턴을 고쳐 써서 만들어지는 것이 아니라는 점입니다. 별도의 XMP 네임스페이스인 http://www.aim.org/pdfe/ns/id/를 사용하고, 버전 값은 두 곳에 나타나야 합니다. 문서 정보 항목으로 한 번, 네임스페이스 한정 XMP 프로퍼티로 한 번. XMP 프로퍼티만 내보내거나 정보 항목만 내보내면 의도는 갖고 있지만 검증은 실패하는 파일이 됩니다

출력 인텐트도 똑같이 구체적인 형태를 요구합니다. PDF/E-1은 서브타입 식별자 ISO_PDFE1을 갖는 임베디드 ICC 프로파일을 요구하고, 프로파일의 컴포넌트 수는 문서가 실제로 쓰는 디바이스 색 계열과 일치해야 합니다. 구현이 조용히 틀어지는 지점이 바로 이 마지막 조항입니다. 인텐트를 미리 골라 두고 잊을 수 없다는 뜻이니까요

디바이스 색에 문서 전체 순회가 필요한 이유는?

색 공간이 페이지 단위 스캔으로는 결코 닿지 않는 리소스 딕셔너리에 숨어 있기 때문입니다. PDF/E-1은 문서 단위에서 DeviceRGB와 DeviceCMYK를 상호 배타적인 계열로 취급하므로, 프로파일 검증은 파일 안 무엇이든 쓰는 모든 디바이스 색 공간을 알아야 합니다. Form XObject에는 자기 리소스가 있고, 패턴에도, 이미지에도 있습니다. 페이지 안의 Form XObject 안의 타일링 패턴은 세 층 깊이이며, 최상위 페이지 리소스만 검사하는 검증기는 두 계열을 모두 쓰는 문서를 그냥 통과시켜 줍니다

그래서 순회는 페이지, 폼, 이미지, 패턴을 하나의 탐색으로 걸어 가면서 색 공간을 등록하고, 그다음에야 문서가 일관되는지, 출력 인텐트가 맞는지 판단합니다. 같은 논리가 프리플라이트 아키텍처 전반을 끌고 갑니다. 부분 순회는 거짓 통과를 낳고, 컨포먼스 검사의 거짓 통과는 검사 부재보다 나쁩니다. 증거로 기록되니까요

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('assembly-drawings.pdf');

    if Lib.SetPDFEMode(1) = 0 then
      raise Exception.Create('PDF/E author mode was refused');

    // author 모드는 저장할 때마다 라이프사이클 메타데이터를 맞춰 줍니다.
    // 저장 전에 이 문서가 자기 게이트를 통과할지 물어보세요
    if not Lib.PDFEReadyForSave then
    begin
      Diag := Lib.GetPDFEDiagnostics;
      Writeln('PDF/E blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('assembly-drawings-pdfe.pdf');
  finally
    Lib.Free;
  end;
end;

라이프사이클 메타데이터는 저장 때마다의 의무

PDF/E-1이 요구하는 것은 문서 식별자 이상입니다. 최소 집합에는 미디어 관리 문서 식별자, 버전 식별자, rendition class, 생성 시각, 수정 시각, 메타데이터 시각, 제목이 포함됩니다. 개정 추적용 어휘입니다. 엔지니어링 인도물은 한 번 쓰고 끝나는 게 아니라 재발행될 것으로 기대되기 때문에 존재합니다

구현에 대한 귀결은 이 필드들을 문서 생성 시점에 설정할 수 없다는 것입니다. 모드를 켤 때 수정 시각을 기록하고 그 뒤에 문서를 편집하면, XMP 스냅샷과 실제 문서 상태가 어긋나고 둘을 비교하는 검증기는 아무도 의도하지 않은 불일치를 보고합니다. 그래서 author 모드는 저장 직전마다 이 필드들을 동기화합니다. 메타데이터가 모드를 켰을 때 존재하던 바이트가 아니라 쓰이려는 바이트를 기술하도록 하기 위해서입니다

이것은 컨포먼스 메타데이터에 대한 일반 원칙이며 PDF/E와 분리해서 적어 둘 가치가 있습니다. 파생된 메타데이터는 편집 경로가 아니라 저장 경로에 속합니다. 문서 상태에서 계산되는 필드는 상태가 얼어붙는 순간에 재계산해야 하며, 그렇지 않으면 무효화 장치가 없는 캐시일 뿐입니다

PDFlibPas PDF/E-1 다이어그램: 페이지, Form XObject, 타일링 패턴, 이미지 리소스 딕셔너리를 걸어가며 DeviceRGB와 DeviceCMYK 계열을 수집한 뒤에야 일관성을 판정하는 문서 전체 디바이스 색 순회와, 저장할 때마다 직전에 라이프사이클 메타데이터 필드를 재동기화해 XMP 스냅샷이 쓰이려는 바이트와 일치하도록 하는 author 모드
색 일관성은 하나의 순회가 모든 리소스 딕셔너리에 도달한 뒤에야 판정할 수 있고, 파생된 라이프사이클 메타데이터는 모드를 켠 시점이 아니라 문서 상태가 얼어붙는 순간에 재계산됩니다

콘텐츠 검증을 엄격하게 만드는 그 규칙

PDF/E-1은 호환성 섹션 연산자가 알 수 없는 콘텐츠를 흡수하는 것을 허용하지 않습니다. 보통 PDF에서 BXEX는 소비자가 인식하지 못하는 연산자를 무시해야 하는 영역을 감쌉니다. 이전 리더를 깨뜨리지 않고 더 새로운 구성물을 내보낼 수 있게 해 주는 탈출구죠. PDF/E-1에서는 그 탈출구가 닫히므로, 프리플라이트가 인식하지 못하는 연산자는 호환성 섹션 안팎과 무관하게 무조건 보고됩니다

검증기에 미치는 영향은 큽니다. 이해하지 못하는 영역을 건너뛸 수 없으므로 피연산자 파서는 모든 콘텐츠 스트림의 모든 연산자를 실제로 파싱해야 합니다. 여기에 상한이 등장합니다. 순회는 중첩 128레벨, 백만 오브젝트, 콘텐츠 64MiB로 제한되며, 이 한계들은 성능 튜닝이 아닙니다. 적의 있건 단순히 깨졌건, 어떤 파일은 순환이 있는 오브젝트 그래프나 재귀 검증기를 스택 오버플로로 몰아 넣는 중첩 깊이를 내밀 수 있고, 그 한계들이 검증 패스가 서비스 거부 공격 경로가 되는 것을 막아 줍니다. 같은 방어 자세는 신뢰할 수 없는 PDF 안전하게 파싱하기에서 다룹니다

// 직접 만들지 않은 파일을 문서 인스턴스에 로드하지 않고
// 단독으로 검증합니다
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('incoming.pdf', fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFE(Stream, '', 0, Issues) = 0 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/E: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

저장 게이트가 수리하는 것과 거부하는 것

게이트는 작업을 두 단계로 나누는데, 이 분할 자체가 써먹을 만한 설계 아이디어입니다. 먼저 안전하게 고칠 수 있는 것들을 정규화합니다. 어노테이션 인쇄 플래그, 텍스트 어노테이션의 줌 금지·회전 금지 플래그, 폼 딕셔너리의 모양 생성 플래그입니다. 이것들은 프로파일 하에서 정답 값이 하나뿐이고 정보량이 없는 설정들이므로, 조용히 고치는 게 맞고 여기서 거부하는 건 소심한 고집일 뿐입니다

그다음에는 문서의 의미를 바꾸지 않고는 고칠 수 없는 제약들을 검사합니다. 버전, 식별, 암호화, 출력 인텐트, 디바이스 색 일관성, 동적 폼 콘텐츠의 존재입니다. 이 중 하나라도 실패하는 문서는 거부됩니다. 작성자를 대신해 출력 인텐트를 지어내거나 색 계열을 골라 주면 검증은 통과하지만 콘텐츠를 잘못 표현하는 파일이 되기 때문입니다

Delphi용 PDFlibPas PDF/E-1 저장 게이트 다이어그램: 중첩 128레벨, 백만 오브젝트, 64MiB 상한 아래에서 모든 콘텐츠 스트림 연산자를 훑는 상한 있는 프리플라이트, 어노테이션 인쇄·줌·회전 플래그의 조용한 수리, 잘못된 버전·식별·암호화·출력 인텐트·디바이스 색·동적 폼 콘텐츠의 거부, 그리고 GetPDFEDiagnostics를 통한 블로커 보고
게이트는 정보를 담지 않는 것만 조용히 고치고, 수리가 왜곡할 모든 제약은 거부하며, 바이트 하나가 디스크에 닿기 전에 거부를 GetPDFEDiagnostics를 통한 블로커 목록으로 바꿉니다

저장 전에 GetPDFEDiagnostics로 진단을 읽어 오면 그 거부가 실패한 연산이 아니라 실행 가능한 목록이 됩니다. 배치 파이프라인이라면 모든 문서에 호출하고 파일별로 블로커를 로그에 남긴 뒤 실패를 사람이 보는 큐로 라우팅하세요. raise하는 저장보다 훨씬 쓸모 있습니다. 블로커는 보통 뭉쳐 나오거든요. 같은 출력 인텐트 누락으로 실패하는 마흔 개 문서는 수정 한 번이지 마흔 번이 아닙니다

아카이브 프로파일 사이의 선택

인도물이 개정 라이프사이클을 가진 엔지니어링 문서이고, 특히 출력이 플로터와 대형 프린터로 가서 디바이스 색 일관성이 중요하다면 PDF/E-1이 옳은 타깃입니다. 목표가 일반적인 문서의 장기 가독성이라면 PDF/A가 옳은 타깃이고, 검증기 지원이 가장 넓은 프로파일이기도 합니다. 둘은 호환이 아니며, 한쪽을 만족하고 다른 쪽은 실패하는 문서가 얼마든지 있습니다

Delphi용 PDFlibPas가 PDF/E-1과 PDF/A 아카이브 프로파일을 비교하는 판단 다이어그램: 자체 XMP 네임스페이스와 ISO_PDFE1 출력 인텐트로 개정 라이프사이클, 플로터 색, 계약상 검증이 필요한 엔지니어링 인도물에는 PDF/E-1, 검증기 지원이 가장 넓은 일반 장기 가독성에는 PDF/A
끝단에서 파일을 검증하는 사람이 누구인지에서 출발하세요: 프로파일들은 서로 다른 식별, 메타데이터, 색 보장을 요구하며, 한쪽을 만족하고 다른 쪽을 실패하는 문서가 있습니다

선택한다면 끝단에서 파일을 검증하는 사람이 누구인지에서 출발하세요. PDF/A 검증 도구는 어디에나 있고, PDFlibPas의 해당 프리플라이트는 PDF/A와 PDF/UA 프리플라이트에 설명되어 있습니다. PDF/E 검증은 더 전문적이며 기본값이라기보다 대개 계약 요건입니다. 기존 아카이브를 원래 작성되지 않았던 프로파일까지 끌어올려야 한다면 메타데이터 수리와 함께 PDF/A로 변환의 메타데이터 수리 경로가 따를 패턴이고, 같은 형태가 여기에도 적용됩니다. 식별하고, 안전한 것은 수리하고, 나머지는 목록과 함께 거부하세요

author 모드, 상한 있는 콘텐츠 프리플라이트, 단독 컴플라이언스 검사는 모두 PDFlibPas Delphi PDF 라이브러리에 들어 있습니다. 문서를 프로파일 하에서 만들고 나서 별도 코드 경로로 독립적으로 검증할 수 있는데, 컨포먼스 클레임을 위해 믿을 만한 유일한 구성입니다