PDFlibPas는 Delphi에서 대상 part를 2로 지정해 TPDFlib.CheckFilePDFUACompliance를 호출함으로써 PDF/UA-2(ISO 14289-2) 파일을 검증합니다. 이 호출은 별도의 PDF/UA-2 프로파일을 구동합니다: 구조 역할을 PDF 2.0 네임스페이스와 /RoleMapNS를 통해 해석하고, 잘리거나 읽을 수 없는 입력은 실패로 보고하며, 완전한 인증을 주장하지 않는 커버리지 리포트를 반환합니다
마지막 부분은 겉보기보다 훨씬 중요합니다. 접근성 검증 분야에서는 초록 체크마크가 조달 스프레드시트에 복사된 뒤 다시는 의심받지 않기 때문입니다. 표준의 절반을 조용히 건너뛰고도 "pass"를 출력하는 검증기는 검증기가 아예 없는 것보다 나쁩니다. 미지의 것을 거짓된 기지로 바꿔 버리기 때문입니다. PDFlibPas는 정반대 입장을 취합니다: 기계적으로 검사할 수 있는 것은 검사하고, 검사하지 못한 것은 분명히 말하며, "끝까지 완료하지 못했다"는 결과 하나하나를 성공이 아니라 발견 사항으로 다룹니다
PDF/UA-1 규칙으로는 PDF/UA-2를 검증할 수 없는 이유
PDF/UA-2는 part 번호만 올린 PDF/UA-1이 아닙니다. PDF/UA-1은 PDF 1.7과 단일한 평면적 role map 위에 놓여 있고, PDF/UA-2는 PDF 2.0(ISO 32000-2) 위에 놓여 있으며, 여기서는 모든 구조 요소가 네임스페이스에 속할 수 있고 네임스페이스마다 자체 role map을 갖습니다. UA-1 검사기를 UA-2 파일에 대면 실제 문제를 놓치는 동시에 가짜 문제를 만들어 냅니다
가짜 문제야말로 곤란한 부분입니다. PDF/UA-1 검증기는 전형적으로 Note 요소가 ID를 가져야 한다는 규칙과 번호 없는 H 헤딩의 사용 방식에 관한 규칙을 강제합니다. 두 규칙 모두 그대로 이전되지 않습니다. PDF 2.0 구조 네임스페이스에서는 각주를 FENote로 표현하고, 헤딩은 6단계 상한 없이 H1부터 Hn까지입니다. PDF/UA-2는 한 발 더 나아가 번호 없는 H 역할을 아예 금지합니다. 그 트리에 UA-1 헤딩 로직을 적용하면 표준이 허용하는 것을 두고 불평하거나, 표준이 금지하는 단 하나의 것을 그냥 통과시키게 됩니다
그래서 PDFlibPas의 PDF/UA-2 프로파일은 기존 경로에 플래그를 붙인 것이 아니라 별도의 코드 경로입니다. UA-1 조항 번호를 재사용하는 것도 거부합니다. UA-1 검사기와 공유하는 진단 항목은 UA-2 리포트에서 ISO 14289-2로 다시 매핑되고, 조항 번호가 표준 본문과 대조 검증되지 않은 경우 리포트는 추측 대신 조항 필드를 비워 둡니다. 빈 조항은 확신에 찬 조항 번호보다 시원찮지만, 아무도 매핑을 확인하지 않았다면 방어 가능한 유일한 출력입니다
PDFlibPas가 RoleMapNS를 통해 역할을 해석하는 방식
PDFlibPas는 네임스페이스를 넘나드는 /RoleMapNS 체인을 따라가며 모든 구조 역할을 알려진 네임스페이스의 표준 역할로 해석하고, 커스텀 역할에서 끝나거나, 루프를 돌거나, 매핑 규칙을 깨는 체인은 모두 보고합니다. 이것이 PDF/UA-2 구조 검증의 핵심입니다. 보조 기술은 표준 역할만 이해하기 때문입니다. 커스텀 /InvoiceLine은 P나 TR 같은 것에 도달하기 전까지는 아무 의미가 없습니다
리졸버는 요소의 /S 이름과 /NS 네임스페이스에서 출발합니다. 네임스페이스가 없으면 요소는 암묵적으로 PDF 1.7 표준 네임스페이스에 속하며 구조 트리 루트의 구식 /RoleMap이 적용됩니다. 네임스페이스가 있으면 네임스페이스 딕셔너리 자신의 /RoleMapNS가 적용되고, 매핑은 2요소 배열을 통해 다른 네임스페이스의 역할을 가리킬 수 있습니다. 여기서 나오는 검사 항목은 구체적입니다:
- 구조 요소에서 참조하는 네임스페이스는 유효한
/NSURI를 가진 간접 딕셔너리여야 하고 구조 트리 루트의/Namespaces배열에 나열되어 있어야 합니다 (진단 10054) - 커스텀 역할은 최종적으로 PDF 1.7 네임스페이스, PDF 2.0 네임스페이스(
http://iso.org/pdf2/ssn), 또는 MathML 네임스페이스의 표준 역할로 해석되어야 합니다; 순환 체인과 막다른 길은 10055로 보고됩니다 - 표준 역할은 커스텀 역할로 재매핑될 수 없고, 역할은 자신이 명시적으로 속한 네임스페이스 안에서 재매핑될 수 없습니다
- 구조 루트는 PDF 2.0 네임스페이스의
Document요소를 정확히 하나 포함해야 하고(10057), 모든 요소의/P는 실제 부모를 가리켜야 합니다(10058) - 번호 없는
H, 또는 PDF 2.0 역할로 매핑되지 않은 구식 PDF 1.7Note는 10056으로 보고됩니다
여러분의 도구에 그대로 옮겨 둘 만한 세부 사항 하나: PDFlibPas는 트리에서 도달 가능한 매핑만이 아니라 선언된 모든 매핑을 검증합니다. 현재 어떤 요소도 사용하지 않는 role map의 깨진 항목도 여전히 잠재 결함입니다. 다음 콘텐츠 업데이트가 그 항목을 사용하기 시작할 테니까요. MathML도 정직하게 다룹니다. MathML 네임스페이스는 유효한 해석 대상으로 인식하지만 MathML 어휘 자체는 검증하지 않으며, 커버리지 리포트에 그 사실이 명시됩니다
Delphi에서 PDF/UA-2 검사 실행하기
TPDFlib.CheckFilePDFUACompliance는 한 번의 호출로 끝나는 진입점입니다: 파일, 비밀번호, 대상 part, 옵션을 넘기면 코딩된 이슈를 담은 문자열 리스트 핸들을 돌려줍니다. 핸들이 0이면서 LastErrorCode가 0이면 구현된 검사에서 아무것도 발견되지 않았다는 뜻이고, 0이 아닌 LastErrorCode는 호출 자체가 실패했음을 의미합니다
var
Lib: TPDFlib;
Issues, I: Integer;
begin
Lib := TPDFlib.Create;
try
// TargetPart: 0 = XMP pdfuaid:part에서 감지, 1 = PDF/UA-1, 2 = PDF/UA-2
// Options: 1 = 첫 실패에서 중단, 0 = 모든 이슈 수집
Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
if Issues = 0 then
begin
if Lib.LastErrorCode <> 0 then
Writeln('Validation did not run, error ', Lib.LastErrorCode)
else
Writeln('No issues from the implemented PDF/UA-2 checks');
end
else
try
for I := 1 to Lib.GetStringListCount(Issues) do // 항목은 1부터 시작
Writeln(Lib.GetStringListItem(Issues, I));
finally
Lib.ReleaseStringList(Issues);
end;
finally
Lib.Free;
end;
end;
대상 part는 신중하게 고르세요. 0을 지정하면 XMP 메타데이터에서 pdfuaid:part를 읽어 그에 맞는 프로파일을 고르는데, 이는 범용 수신함 검증기에는 적합합니다. 자체 출력 파이프라인이라면 2를 명시적으로 넘기세요. 생성기가 식별 정보를 쓰는 것을 잊으면 자동 감지는 UA-1 규칙으로 폴백되어 엉뚱한 표준을 검증하게 됩니다; 명시적으로 2를 넘기면 대신 누락된 part에 대해 진단 10050이, pdfuaid:rev가 2024가 아니면 10051이, 실효 PDF 버전이 2.0이 아니면 10052가 발생합니다. 작성 측에서는 TPDFlib.SetPDFUA2Mode가 이 식별 정보를 기록하고 버전을 2.0으로 올리며 PDF 2.0 구조 네임스페이스를 기본값으로 선택합니다; 네임스페이스 모델 자체는 PDF 2.0, PDF/A-4, PDF/UA-2 네임스페이스 훑어보기에서 설명합니다
검증기가 예산을 다 쓰면 어떻게 되나요?
PDFlibPas는 fail-closed 원칙으로 동작합니다: PDF/UA-2 검사기가 탐색 한도, 순환, 파서 한도, 열 수 없는 문서에 부딪히면 진단을 발행하고, 진단이 없는 결과가 pass로 승격되는 일은 없습니다. 진단 10059는 바로 이 "리더가 끝까지 완료하지 못했다"는 경우를 위해 예약되어 있습니다
예산은 명시적이며 커버리지 JSON에 공개되어 있습니다: 구조 깊이 256, 구조 오브젝트 100,000개, role map 체인당 256홉입니다. 이 숫자들이 존재하는 이유는 구조 트리가 신뢰할 수 없는 입력에서 나오기 때문입니다. 악의적이든 단순히 깨졌든, 파일은 요소를 수천 단계 깊이로 중첩시키거나 자식이 조상을 다시 가리키게 하거나 수백 개의 네임스페이스를 연결하는 role map을 만들 수 있습니다. 예산 없는 검증기는 크래시하거나 멈추고, 예산은 있지만 진단이 없는 검증기는 더 교묘하고 나쁜 짓을 합니다: 일찍 멈추고, 읽은 부분에서는 아무 문제를 찾지 못하고, 성공을 보고합니다
같은 원칙이 한 단계 위에서도 적용됩니다. 기반이 되는 리더가 100 미만의 점수를 돌려주는데도 이슈 라인이 없다면 — 어떤 규칙도 실행되기 전에 파싱이 중단될 때 일어날 수 있는 상황입니다 — PDFlibPas는 10059 라인을 삽입하여 빈 리스트가 깨끗한 결과로 오해되지 않게 합니다. 검사 중 예외가 발생해도 10059 라인이 됩니다. 코드 분량은 얼마 안 되지만, 검증 파이프라인이 소유자를 속이는 가장 흔한 경로를 막아 줍니다
PDF/A와 파싱 한 번 공유하기, 그리고 리포트 만들기
배치 작업에서는 검증 파이프라인 API가 파일을 한 번만 파싱하고 공유된 파싱 결과에 대해 여러 검사를 돌립니다. RunPDFUAValidationPipelineTask는 그 계열의 PDF/UA 멤버로, 한 번 호출용 함수와 동일한 대상 part 값을 받으므로 PDF/A + PDF/UA-2 프리플라이트가 큰 문서를 두 번 다 읽는 낭비를 피할 수 있습니다
var
Pipe, PDFAIssues, UAIssues: Integer;
Json: WideString;
begin
Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
if Pipe = 0 then
Exit;
try
PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0); // 1 = PDF/A
UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0); // PDF/UA-2
// ... 이전 예제에서처럼 두 핸들을 검사하고 해제합니다 ...
Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
// ComplianceTests: 1 = PDF/A, 2 = PDF/UA, 0 = 둘 다; ReportFormat 1 = JSON
Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
finally
Lib.ReleaseValidationPipeline(Pipe);
end;
end;
구조화 리포트는 파일이 part 2로 식별될 때 접근성 섹션에 PDF/UA-2와 ISO 14289-2 라벨을 붙이며, 모든 PDF/UA 섹션은 내장된 커버리지 오브젝트와 함께 "fullCertification": false를 실어 나릅니다. 텍스트 프리플라이트 리포트는 구현된 검사만 실행되었으며 완전한 접근성 인증에는 추가 검증과 사람의 검토가 필요하다는 한 줄을 덧붙입니다. 더 넓은 범위의 리포트 포맷, 규칙 ID, CI 게이팅 패턴은 PDF/A 및 PDF/UA 프리플라이트 가이드에 설명되어 있습니다
커버리지 리포트가 실제로 보장하는 것은 무엇인가요?
TPDFlib.GetPDFUAReadValidationCoverageJSON이 보장하는 것은 단 하나입니다: PDF/UA-2 검사기가 무엇을 검토했고 무엇을 검토하지 않았는지의 정확한 목록입니다. part 2의 경우 false로 고정된 fullCertification, checked 배열, unverified 배열, 그리고 탐색 한도를 반환합니다. 파일이 접근 가능하다는 것은 보장하지 않으며, 그렇게 말할 방법을 어떤 코드 경로에도 주지 않도록 설계되어 있습니다
var
Coverage: WideString;
begin
Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
// 검증 결과 대신이 아니라 그 곁에 함께 저장합니다
TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
if Pos('"fullCertification":false', Coverage) > 0 then
Writeln('Machine checks only; schedule a human accessibility review');
end;
checked 목록은 식별 정보(part, revision, prefix), 실효 PDF 버전, 제목 메타데이터, /MarkInfo, DisplayDocTitle, 네임스페이스 참조, role map 해석, 문서 루트, 구조 부모, 트리 순환, 그리고 H와 Note 규칙을 다룹니다. unverified 목록도 마찬가지로 구체적입니다: 콘텐츠 태깅과 읽기 순서, 구조 포함 관계, 폰트와 Unicode 매핑, 주석과 폼, 자연 언어, 대체 설명, MathML 어휘, 그리고 사람에 의한 접근성 검토입니다. 이중 몇 가지는 기계적으로 판정 자체가 불가능합니다. 어떤 파서도 alt 텍스트가 실제로 그 차트를 설명하는지, 읽기 순서가 듣는 사람에게 의미가 통하는지 알려 줄 수 없습니다
커버리지 JSON을 결과의 일부로 다루세요. 모든 검증 실행마다 함께 보관해 두면, 감사인이 특정 날짜의 "pass"가 무엇을 의미했는지 물어올 때 기억과 버전 번호로 재구성하는 대신 정확한 범위를 내밀 수 있습니다
veraPDF 대조 검증
PDF/UA-2 코어는 veraPDF 1.30.3 커맨드 라인 검증기의 ua2 프로파일로 대조 검증되었고, 커버리지가 겹치는 범위에서는 결과가 일치합니다. 별도로 생성된 유효한 코어 샘플은 두 도구 모두를 통과했습니다; pdfuaid:rev 2023을 선언한 샘플과 번호 없는 H 역할을 사용한 샘플은 두 도구 모두에서 실패했습니다
네 번째 샘플이야말로 이야기할 가치가 있습니다. 전혀 태깅되지 않은 드로잉 콘텐츠를 담고 있었는데, veraPDF는 이를 지적한 반면 PDFlibPas 코어 검사는 지적하지 않았습니다. 콘텐츠 태깅이 현재 커버리지 밖에 있기 때문입니다. 바로 이 간극 때문에 content-tagging-and-reading-order가 unverified 목록의 선두에 있습니다. 검증기를 하나 더 돌리는 것은 값싼 보험이며, 두 도구의 불일치는 각자가 검사하는 경계를 찾는 가장 빠른 길입니다. 같은 문제의 작성 측면, 즉 처음부터 통과하는 트리를 만드는 방법은 태그드 PDF 구조 트리 가이드를 참고하세요
FAQ
PDFlibPas는 파일을 PDF/UA-2 준수로 인증하나요?
아니요. PDFlibPas는 PDF/UA-2에 대해 구현된 기계 검사를 실행하고 fullCertification이 항상 false인 커버리지 리포트를 반환하며, 여전히 다른 도구나 사람의 검토가 필요한 영역을 모두 나열합니다
CheckFilePDFUACompliance에는 어떤 TargetPart 값을 넘겨야 하나요?
파일이 반드시 PDF/UA-2여야 한다는 것을 알고 있다면 2를 넘기세요. 식별 정보가 누락되면 보고됩니다. XMP pdfuaid:part에서 part를 자동 감지하려면 0을, PDF/UA-1 규칙을 강제하려면 1을 넘깁니다
PDF/UA-2 리포트에서 진단 10059는 무슨 의미인가요?
진단 10059는 검증기가 완료하지 못했다는 뜻입니다: 파일을 파싱하거나 열 수 없었거나, 탐색 한도를 초과했거나, 순환이 발견되었거나, 예외가 발생한 경우입니다. 항상 실패로 간주되며 결코 pass로 간주되지 않습니다
PDF/UA-2 검증, 공유 검증 파이프라인, PDF 2.0 작성 API는 PDF/A 프리플라이트와 구조화 검증 리포트와 함께 Delphi용 losLab PDF Library에 들어 있습니다