기술 문서

PDFlibPas로 Delphi에서 PDF/X-1a, X-3, X-4 출력하기

PDFlibPas는 Delphi에서 단 한 번의 호출, 즉 TPDFlib.SetPDFXMode로 PDF/X 인쇄 파일을 생성합니다. 이 호출은 PDF/X-1a:2003, PDF/X-3:2003 또는 PDF/X-4를 선택하며, XMP pdfxid 식별 정보를 기록하고 GTS_PDFX 출력 인텐트를 추가하고 /Trapped /False를 선언하고 PDF 버전을 고정한 뒤, 선택한 part가 금지하는 드로잉 호출을 그 시점부터 거부합니다. 저장된 파일은 test 3를 지정한 CheckFileCompliance로 감사합니다

이것이 짧은 답입니다. 긴 답도 중요한데, 인쇄소는 파일이 못생겼다고 거부하지 않고 프리플라이트에서 거부하기 때문입니다. 보통 금요일에, 보통 생성 코드가 미리 잡을 수 있었던 것 때문에 거부합니다: X-1a 작업에 들어간 RGB 로고, 임베드 대신 참조로 넘어간 폰트, 재단 사이즈가 없는 페이지. PDF/X는 이런 실패를 시각적 문제가 아니라 구조적 문제로 만들기 위해 존재하고, 라이브러리 쪽 역할은 애초에 그런 파일을 만들지 않도록 막는 것입니다

Delphi 애플리케이션은 어느 PDF/X part를 목표로 해야 할까요?

수신 워크플로가 달리 말하지 않는 한 PDF/X-4를 고르고, 말한다면 PDF/X-1a를 고르세요. 세 part는 정확히 세 축, 즉 색상, 투명도, 기반 PDF 버전에서 갈리고, 각 축은 PDFlibPas 내부의 게이트 하나에 대응합니다:

  • PDF/X-1a:2003(ISO 15930-4): PDF 1.4 문법, CMYK와 그레이, 스팟 색상만 허용, ICC 기반 색상 없음, 투명도 없음. 파일이 이미 인쇄 색상으로 분판되어 있으므로 프린터는 색상 변환을 전혀 하지 않습니다
  • PDF/X-3:2003(ISO 15930-6): 여전히 PDF 1.4이고 투명도도 여전히 없지만, 컬러 매니지드 색 공간은 허용되며, 출력 인텐트 자체가 RGB 조건을 기술하는 경우 디바이스 RGB도 받아들여집니다
  • PDF/X-4(ISO 15930-7): PDF 1.6 문법, 살아 있는 투명도와 옵셔널 콘텐츠(레이어) 지원. 요즘 RIP들이 이 part 기준으로 만들어집니다. 출력 단계에서 투명도를 평탄화하는 작업이 예전 X-1a 고통의 상당 부분을 차지했으니까요

X-1a는 광고 납품과, 고민 없이 이미징할 수 있는 파일을 원하는 구식 인쇄 워크플로에서 아직도 널리 쓰입니다. 인쇄소가 받아 주는 한 X-4가 더 나은 엔지니어링 선택입니다: 투명도가 그대로 살아남고, 색상 결정은 여러분의 코드가 아니라 실제 인쇄 프로필을 가진 RIP에서 이뤄집니다. 모드 번호는 순서대로 1, 2, 3이며, GetInformation(203)이 현재 활성 모드를 돌려줍니다. PDFlibPas는 PDF 2.0 기반 PDF/X-6 계열을 위해 더 높은 모드 번호도 정의하는데, 이들은 다른 버전 및 출력 인텐트 규칙을 따르며 이 글의 범위 밖입니다

SetPDFXMode가 파일에 기록하는 것

TPDFlib.SetPDFXMode는 PDF/X 리더가 가장 먼저 확인하는 식별 정보와 상용구를 처리하므로, 애플리케이션 코드는 실제 인쇄 프로필과 콘텐츠만 공급하면 됩니다. 구체적으로, 성공한 호출은 네 가지를 합니다:

  • 에디션 문자열을 XMP에 pdfxid:GTS_PDFXVersion으로 기록합니다 (PDF/X-1a:2003, PDF/X-3:2003 또는 PDF/X-4). 2003 에디션은 그 문자열만으로 part를 완전히 식별하므로 별도의 conformance 속성은 기록되지 않습니다
  • 문서에 출력 인텐트가 없으면 추가하고 서브타입을 /S /GTS_PDFX, 즉 PDF/X 리더가 찾는 서브타입으로 설정합니다 (출력 인텐트 딕셔너리는 ISO 32000-1 §14.11.5에 정의됩니다)
  • 문서 정보 딕셔너리에 /Trapped /False를 설정합니다. 라이브러리는 트래핑을 하지 않으며, PDF/X는 트래핑 상태를 미지로 두는 대신 선언할 것을 요구하기 때문입니다
  • 버전 베이스를 고정합니다: X-1a와 X-3는 1.4, X-4는 1.6

저장 시점에 두 가지 수리가 더 일어납니다. TrimBox가 없는 모든 페이지는 MediaBox와 같은 값을 부여받습니다. PDF/X는 페이지마다 선언된 최종 크기를 요구하기 때문입니다 (페이지 경계는 ISO 32000-1 §14.11.2가 다룹니다). 또한 주석에는 표준이 기대하는 인쇄 플래그가 붙습니다. TrimBox 폴백은 레이아웃 결정이 아니라 안전망입니다. 풀블리드 작업에는 더 큰 미디어 박스 안의 진짜 재단 박스가 필요하며, 박스 설정 방법과 상속이 어떻게 발목을 잡는지는 PDFlibPas의 TrimBox, BleedBox, CropBox 기본값 가이드에서 다룹니다

uses
  PDFlibrary;

procedure BuildX1aAd(const OutFile, PressProfile: string);
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.SetPDFXMode(1) <> 1 then          // 1 = PDF/X-1a:2003
      raise Exception.CreateFmt('PDF/X mode refused (error %d)',
        [Pdf.LastErrorCode]);

    // 기본 프로필을 실제 인쇄 조건으로 교체합니다
    if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
      raise Exception.Create('Could not load the CMYK output profile');

    // Embed = 1은 폰트 프로그램 전체를 임베드합니다; PDF/X는 0을 받지 않습니다
    if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
      raise Exception.Create('Font could not be loaded for embedding');

    Pdf.SetTextSize(24);
    Pdf.SetFillColorCMYK(0, 1, 1, 0);           // 프로세스 레드, RGB 없음
    Pdf.DrawText(72, 720, 'Spring catalog, page 1');

    Pdf.SaveToFile(OutFile);
  finally
    Pdf.Free;
  end;
end;

이 코드 스케치의 순서에는 의도가 있습니다. LoadOutputIntentProfile은 이미 존재하는 출력 인텐트의 프로필을 교체하는 것이므로 SetPDFXMode가 인텐트를 만든 후에 실행되어야 합니다. 이 호출은 무에서 유를 창조하지도 못합니다: 출력 인텐트가 없는 문서에서는 그냥 0을 반환합니다

기본 출력 인텐트를 교체해야 하는 이유

모드 1에서 3에 대해 SetPDFXMode가 추가하는 출력 인텐트는 DeviceCMYK를 선언하지만, 그 뒤에 있는 ICC 바이트는 컴포넌트 수를 4로 고쳐 쓴 라이브러리 번들 sRGB 프로필입니다. 이 기본값은 하위 호환을 위해 존재하며 파일을 구조적으로 PDF/X로 만들기에는 충분하지만, 임베드된 프로필은 어떤 인쇄 조건도 기술하지 않습니다. 프로필 헤더를 들여다보는 프리플라이트 도구는 CMYK 라벨을 단 RGB 프로필을 보게 되고, 인텐트에 근거해 교정쇄를 출력하는 인쇄사는 엉터리를 받게 됩니다

따라서 실제 인쇄 작업에서는 LoadOutputIntentProfile(FileName, 'DeviceCMYK')를 필수로 취급하고, 인쇄 소가 용지와 인쇄기에 대해 지정한 ICC 프로필을 넘기세요. TPDFlib.LoadOutputIntentProfile은 DestOutputProfile 스트림을 교체하고 /N을 갱신하며, 인텐트 딕셔너리의 나머지 부분은 기록된 그대로 둡니다. 특히 기본 인텐트의 출력 조건 식별자와 설명 문자열은 여전히 sRGB 스타일 값을 담고 있으므로, 인쇄소가 임베드된 프로필이 아니라 식별자로 매칭한다면 납품 전에 무엇을 확인하는지 그들과 확인하세요. 이것은 코드가 뭘 잘못해서가 아니라 2003년대 기본값의 진짜 간극입니다

함정 1: 여러분이 고르지 않은 버전

새 PDFlibPas 문서는 라이브러리 기본값인 PDF 1.5로 시작하는데, 이는 PDF/X-1a와 PDF/X-3가 요구하는 1.4 베이스보다 높습니다. TPDFlib.SetPDFXMode는 흔한 경우 이를 조용히 해결합니다: 저장 버전이 잠기지 않았다면 문서 버전을 part 베이스로 낮추고 계속 진행합니다. 최초 구현은 대신 거부했고, 새 문서에 대한 모든 SetPDFXMode(1)이 0을 반환했습니다. 종이 위에서는 옳지만 실전에서는 무용지물인 규칙의 좋은 예죠

이 거부는 옳은 경우 딱 하나, 거기서는 여전히 존재합니다. 앞서 무언가가 베이스보다 높은 버전으로 LockSaveVersion을 호출했다면 SetPDFXMode는 LastErrorCode 601과 함께 0을 반환합니다. 호출자가 의도적으로 버전을 고정한 것이고 라이브러리가 그것을 무시하지 않기 때문입니다. 버전 잠금 코드가 무관한 이유로 버전을 잠가 두는 공유 문서 셋업 코드에서, X-1a 작업이 죽는 지점이 바로 여기입니다:

Pdf := TPDFlib.Create;
try
  Pdf.LockSaveVersion('1.7');     // 범용 "하우스 기본값" 헬퍼가 설정
  if Pdf.SetPDFXMode(1) = 0 then
    // LastErrorCode = 601: 1.7이 잠김, PDF/X-1a에는 1.4 필요.
    // 인쇄 작업에서는 잠금을 건너뛰세요. PDF/X-4도 여기서는 탈출구가 아닙니다:
    // 1.6 베이스 역시 1.7 잠금 아래라 같은 방식으로 실패합니다.
    LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
  Pdf.Free;
end;

같은 게이트가 관련된 제한도 설명합니다. PDF/X 모드는 문서 생성 스위치입니다: SetPDFXMode는 새 문서에서는 동작하고 파일에서 로드한 문서에서는 0을 반환합니다. 라이브러리가 직접 쓰지 않은 콘텐츠를 보증할 수 없기 때문입니다. 기존 PDF를 PDF/X로 변환하는 것은 플래그 하나가 아니라 검사와 수리가 필요한 별개의 작업입니다

함정 2: 조용히 거부하는 게이트

PDF/X 모드가 활성화되면 PDFlibPas는 선택한 part가 금지하는 콘텐츠 호출을, 나중에 파일이 프리플라이트에서 떨어지게 두는 대신, 호출하는 그 순간 거부합니다. 거부 대상은 디바이스 RGB 채우기와 선(전체 X-1a; 출력 인텐트가 RGB가 아닌 한 X-3와 X-4), X-1a의 ICC 기반 색상, X-1a와 X-3의 투명도와 블렌드 모드, X-1a와 X-3의 옵셔널 콘텐츠, JavaScript 액션, 임베디드 파일 첨부, LZW 이미지 필터, 임베드되지 않을 Standard 14 폰트, 공개키 암호화입니다. Encrypt를 통한 비밀번호 암호화는 생성 시점에는 게이트가 없지만, 검증기는 암호화된 파일을 모두 지적하므로 인쇄 작업은 암호화하지 않은 채로 두세요

거부들이 공유하지 않는 것은 에러 코드이며, 여기가 함정입니다. SetFillColor, SetLineColor, ICC 기반 색상 호출, EncryptPublicKey의 색 공간 게이트는 LastErrorCode를 601로 설정합니다. PDF/A 게이트도 같이 쓰는 라이브러리 준수 코드죠. 반면 투명도, JavaScript, 첨부, LZW, AddStandardFont 게이트는 그저 0을 반환하고 LastErrorCode를 건드리지 않으므로, 이전 호출의 값이 그대로 남아 있을 수 있습니다. 드로잉 호출 묶음 뒤에 LastErrorCode만 확인하는 코드는 거부의 절반을 놓치므로, 반환값을 하나하나 확인하세요:

procedure Require(Ok: Integer; const What: string);
begin
  if Ok = 0 then
    raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;

Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency');   // X-1a / X-3에서는 0
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
  'app.alert(1)', 0), 'JavaScript link');           // 모든 part에서 0

폰트는 별도의 경고가 필요합니다. AddStandardFont는 PDF/X에서 0을 반환합니다. 라이브러리가 Standard 14 아웃라인을 함께 배포하지 않으므로 임베드할 수 없기 때문입니다. AddTrueTypeFont는 다릅니다: PDF/A 모드에서는 Embed = 0을 전체 임베드로 승격해 주지만, PDF/X 모드에서는 그러지 않으므로 참조된 시스템 폰트가 파일에 들어가고 검증기만 그것을 잡아 냅니다. 1(전체) 또는 2(서브셋)를 직접 넘기거나, 셋업 시점에 SetEmbedAllFonts(1)를 한 번 호출해서 빠져나가는 0이 없도록 하세요

인쇄소로 보내기 전에 PDF/X 파일을 어떻게 검증하나요?

저장된 파일에 대해 ComplianceTest = 3으로 CheckFileCompliance를 실행하세요. TPDFlib.CheckFileCompliance는 기록된 지적이 없으면 0을 반환하고, 그렇지 않으면 PDFX- 접두사가 붙은 라인들을 담은 문자열 리스트 핸들을 반환합니다. PDF/X 감사는 PDFlibPas의 PDF/A 및 PDF/UA 프리플라이트와 같은 스트리밍 리더로 파일을 읽으며, 인쇄 프리플라이트가 가장 먼저 확인하는 규칙들을 검사합니다:

  • 파일이 암호화되어 있지 않고, XMP가 알려진 GTS_PDFXVersion을 담고 있으며, PDF 버전이 part 베이스를 초과하지 않습니다
  • 출력 인텐트가 정확히 하나 존재하고, 서브타입이 GTS_PDFX이며, 목적지 프로필과 출력 조건 식별자를 담고 있습니다
  • 정보 딕셔너리가 /Trapped를 선언합니다
  • 모든 페이지가 MediaBox 안에 놓이는 TrimBox를 가지고, BleedBox가 있는 경우 TrimBox를 감쌉니다
  • 페이지에서 쓰인 모든 폰트가 임베드된 프로그램을 가지며, Type0 폰트는 하위 폰트를 통해 검사됩니다
  • 금지된 주석 타입(RichMedia, Screen, 3D, Movie, Sound, FileAttachment)이 없고, JavaScript 열기 액션이나 문서 수준 JavaScript가 없으며, 임베디드 파일이 없고, 오브젝트 그래프 어디에도 LZWDecode 필터가 없습니다
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
  Report: TStrings): Boolean;
var
  ListId, I: Integer;
begin
  ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0);  // 3 = PDF/X
  Result := ListId = 0;
  if Result then
    Exit;
  try
    for I := 1 to Pdf.GetStringListCount(ListId) do       // 항목은 1부터 시작
      Report.Add(Pdf.GetStringListItem(ListId, I));
  finally
    Pdf.ReleaseStringList(ListId);
  end;
end;

그 pass가 무엇을 의미하는지는 분명히 하세요. PDFlibPas 검증기는 위에 나열한 구조적 규칙을 감사하며, 그 규칙들이야말로 생성기 실수를 잡는 것들입니다. 색상 정밀 프리플라이트는 아닙니다: 총 잉크 커버리지, 이미지 해상도, 임베드된 프로필이 인쇄소가 운용하는 조건과 맞는지는 검사하지 않습니다. 빌드나 익스포트 파이프라인의 게이트로 쓰고, 인쇄소 고유의 검사는 인쇄소 프리플라이트 프로필에 맡기세요. 어차피 그쪽에서 돌릴 검사입니다

FAQ

PDFlibPas는 PDF/X-4 투명도를 지원하나요?

예. SetPDFXMode(3)에서 PDFlibPas는 투명도, 블렌드 모드, 옵셔널 콘텐츠 그룹을 허용하고 PDF 1.6 파일을 기록합니다. 같은 호출들이 PDF/X-1a와 PDF/X-3에서는 0을 반환합니다

PDFlibPas는 기존 PDF를 PDF/X로 변환할 수 있나요?

아니요. SetPDFXMode는 현재 TPDFlib 세션에서 생성된 문서에만 적용되며 로드한 파일에는 0을 반환합니다. 기존 PDF라도 CheckFileCompliance와 test 3으로 감사할 수는 있습니다

거부된 PDF/X 호출은 어떤 에러 코드를 반환하나요?

색 공간과 공개키 암호화 거부는 LastErrorCode를 601로 설정합니다. 투명도, JavaScript, Standard 14 폰트 같은 다른 게이트는 0만 반환하므로, 함수 반환값을 항상 확인하세요

PDF/X 출력, PDF/X 검증기, 그리고 인쇄와 아카이빙 도구 모음의 나머지는 Delphi용 losLab 네이티브 Object Pascal PDF 라이브러리인 PDFlibPas의 일부입니다. 에디션, 플랫폼, 전체 API 레퍼런스는 PDFlibPas Delphi PDF Library 제품 페이지를 참고하세요