Artykuł techniczny

Walidacja PDF/X w Delphi za pomocą Komponentu PDFium

Komponent PDFium dla Delphi waliduje pliki PDF/X gotowe do druku za pomocą metody TPdf.ValidatePdfX, która implementuje sprawdzanie normy ISO 15930 na dwóch poziomach: ośmiu testów zawartości na poziomie bajtów (zakaz kompresji LZW, JavaScriptu, pól formularzy, referencji OPI, brakującego TrimBoxa, nieustawionego klucza Trapped i innych) oraz przebiegu modelu obiektowego PDFium korzystającego z funkcji FPDFFont_GetIsEmbedded do weryfikacji osadzania czcionek w każdym obiekcie tekstowym na każdej stronie. Wynikiem jest rekord TPdfXValidationResult określający wykryty poziom zgodności i wymieniający każde naruszenie jako typ wyliczeniowy (enum), dzięki czemu Twoja aplikacja w Delphi może poinformować klienta, dlaczego plik zostanie odrzucony w drukarni, zanim ktokolwiek naświetli matrycę

Jeśli zdarzyło Ci się wysłać zlecenie do drukarni i otrzymać je z powrotem z lakoniczną odmową — „brak TrimBox”, „czcionki nie są osadzone”, „Trapped nie jest ustawione” — dobrze znasz koszt zbyt późnego wykrycia błędów. PDF/X to prepressowy odpowiednik PDF/A: podczas gdy archiwalny PDF/A gwarantuje, że dokument będzie wyglądał identycznie za kilkadziesiąt lat, PDF/X daje pewność, że dokument zostanie zrasteryzowany, rozseparowany i przycięty identycznie na dowolnym naświetlarzu (RIP) jutro rano. Te dwa standardy dzielą ze sobą pewne mechanizmy (identyfikację XMP, OutputIntents, osadzone profile ICC), ale odpowiadają na inne pytania, dlatego komponent dostarcza osobne walidatory dla każdego z nich — temat PDF/A został omówiony w artykule o walidacji PDF/A za pomocą Komponentu PDFium

Czego norma ISO 15930 naprawdę wymaga od gotowego do druku pliku PDF?

Norma ISO 15930 powstała, aby umożliwić tzw. ślepą wymianę (blind exchange): projektant przekazuje plik drukarni, z którą nigdy wcześniej nie rozmawiał, a ta jest w stanie poprawnie go wydrukować bez telefonów, e-maili o brakujących czcionkach czy zapomnianych obrazów, które pozostały na komputerze projektanta. Każda reguła w standardzie służy temu celowi. Czcionki must być osadzone, ponieważ nie można zakładać, że naświetlarz odbiorcy je posiada. Zewnętrzne odwołania są zabronione, ponieważ plik musi być kompletny sam w sobie. Interaktywne funkcje również są wykluczone, ponieważ farba drukarska nie obsługuje zdarzeń kliknięcia

Komponent PDFium rozpoznaje trzy rodziny zgodności i zgłasza je za pomocą typu wyliczeniowego TPdfXConformance w wyniku walidacji: pxc1a dla PDF/X-1a:2001 (norma ISO 15930-1, rygorystyczny profil CMYK oraz kolory dodatkowe na bazie PDF 1.3/1.4), pxc3 dla PDF/X-3:2002 (norma ISO 15930-3 dopuszczająca RGB, Lab i kolory zarządzane przez ICC) oraz pxc4 dla PDF/X-4:2010 (norma ISO 15930-7 pozwalająca na aktywną przezroczystość i warstwy na bazie PDF 1.6). Plik, który nie nosi żadnych oznaczeń PDF/X, zwraca status pxcNone, co również stanowi przydatną odpowiedź: dokument nie deklaruje gotowości do druku, a reszta raportu walidatora wyjaśnia, czego brakuje do jej osiągnięcia

Zakazy te stają się zrozumiałe, gdy spojrzymy na nie z perspektywy dostawcy systemów RIP. Filtr /LZWDecode jest zabroniony w każdym wariancie PDF/X, aby zgodny z normą odbiorca nigdy nie zależał od algorytmu o skomplikowanej historii kompatybilności i licencji; kompresja Flate wykonuje to samo zadanie bez dodatkowego bagażu. Skrypty JavaScript, pola AcroForm oraz słowniki dodatkowych akcji /AA są zabronione, ponieważ plik do druku musi być niezmiennym opisem śladów na papierze — cokolwiek, co może zmienić wygląd dokumentu w momencie jego otwarcia, łamie gwarancję, że to, co zatwierdzono, jest dokładnie tym, co zostanie wydrukowane. Zastępcze elementy OPI (Open Prepress Interface) są również zakazane, ponieważ z definicji są odwołaniami do obrazów o wysokiej rozdzielczości przechowywanych w innym miejscu, a to „inne miejsce” to dokładnie to, czego ślepa wymiana stara się uniknąć

Dlaczego drukarnie odrzucają pliki PDF bez TrimBoxa?

TrimBox to obszar gotowej strony — prostokąt, który pozostaje po cięciu gilotyną. MediaBox, który posiada każda strona PDF, to po prostu arkusz: zawiera spady, pasery, celowniki rejestracyjne i paski kolorów. Oprogramowanie do impozycji układa strony na arkuszu drukarskim według ich TrimBoxów; bez niego operator musi zgadywać, gdzie kończy się Twoja wizytówka, a błędne założenie może obciąć spady lub pozostawić biały margines na krawędzi. Właśnie dlatego norma ISO 15930 wymaga obecności TrimBoxa (lub ArtBoxa) na każdej stronie, a funkcja ValidatePdfX zgłasza błąd pvxiMissingTrimBox, gdy nie znajdzie klucza /TrimBox na którejkolwiek stronie dokumentu

Klucz /Trapped odpowiada na inne pytanie produkcyjne. Trapping to technika prepress polegająca na minimalnym nakładaniu na siebie sąsiednich kolorów, aby drobne przesunięcia papieru na maszynie nie tworzyły białych przerw między nimi. Drukarnia musi wiedzieć, czy ta praca została już wykonana: nałożenie trappingu na plik już poddany tej operacji podwaja overlaps, a pominięcie go dla pliku nietraponowanego grozi widocznymi szparami. PDF/X wymaga zatem, aby słownik Info jawnie deklarował status /Trapped /True lub /Trapped /False — brak tego klucza lub ustawienie go jako /Unknown zmusza człowieka do ręcznej inspekcji pliku, doomed to failure, co narusza ideę ślepej wymiany. Komponent oznacza tę sytuację jako błąd pvxiTrappedNotSet

Uruchamianie dwupoziomowej walidacji za pomocą TPdf.ValidatePdfX

Metoda TPdf.ValidatePdfX nie przyjmuje argumentów i zwraca rekord TPdfXValidationResult składający się z trzech pól: Conformance (wykryty typ PDF/X), Issues (pascalowy zbiór wartości TPdfXValidationIssue) oraz pola pomocniczego IsCompliant. Wewnętrznie serializuje ona załadowany dokument do strumienia w pamięci, uruchamia weryfikator na poziomie bajtów, a następnie przechodzi po modelu obiektowym PDFium, sprawdzając osadzenie każdej czcionki. Minimalna walidacja wygląda następująco:

uses PDFium, FPdfPdfx;

procedure CheckPrintReadiness(const FileName: string);
var
  Pdf: TPdf;
  Res: TPdfXValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;

    Res := Pdf.ValidatePdfX;
    Writeln('Detected conformance: ',
      Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...

    if Res.IsCompliant then
      Writeln('PDF/X checks passed')
    else
    begin
      if pvxiMissingTrimBox in Res.Issues then
        Writeln('REJECT: no /TrimBox on the pages');
      if pvxiTrappedNotSet in Res.Issues then
        Writeln('REJECT: /Trapped missing or /Unknown');
      if pvxiPdfiumFontNotEmbedded in Res.Issues then
        Writeln('REJECT: a page uses a non-embedded font');
      if pvxiLzwForbidden in Res.Issues then
        Writeln('REJECT: LZWDecode filter present');
    end;
  finally
    Pdf.Free;
  end;
end;

Ponieważ Issues jest zwykłym pascalowym zbiorem, możesz nim zarządzać według własnych potrzeb — traktować problemy strukturalne jako powód do twardego odrzucenia pliku, zgłaszać pvxiMissingTitle (wskazówka w standardzie, nie twardy wymóg) jako ostrzeżenie, a resztę po prostu logować. Ten sam typ rekordu zasila również generator raportów komponentu, więc jeśli wolisz wygenerować czytelny dla człowieka dokument zamiast badać wartości wyliczeniowe w kodzie, schemat opisany w artykule o tworzeniu CLI do raportów walidacji ma zastosowanie również do PDF/X

Co wykrywa weryfikator na poziomie bajtów, a co pomija?

Weryfikator na poziomie bajtów wykonuje skanowanie tokenów struktury dokumentu z pominięciem treści strumieni (stream bodies), dzięki czemu plik JPEG zawierający ciąg bajtów /JavaScript nie wywoła fałszywego alarmu. Oprócz badania znaczników (XMP pdfxid:GTS_PDFXVersion, profilu ICC w OutputIntent, wpisu /ID w trailerze oraz zakazu szyfrowania), weryfikator wykonuje osiem testów zawartości, z których każdy ma własną wartość wyliczeniową:

  • pvxiLzwForbidden — filtr /LZWDecode pojawia się gdziekolwiek w pliku (zabroniony we wszystkich wariantach PDF/X)
  • pvxiJavaScriptForbidden — obecna jest akcja /JavaScript lub drzewo nazw JavaScript
  • pvxiFormFieldsForbidden — istnieje słownik /AcroForm lub wpis /XFA
  • pvxiAdditionalActions — obecny jest słownik dodatkowych akcji /AA
  • pvxiEmbeddedFilesForbidden — obecny jest wpis /EmbeddedFiles lub adnotacja /FileAttachment
  • pvxiOpiForbidden — wpis /OPI lub /Alternates odnosi się do wymiennej zawartości obrazu
  • pvxiMissingTrimBox — brak TrimBoxa na dowolnej stronie
  • pvxiTrappedNotSet — brak wartości /Trapped lub ustawienie jej na /Unknown

Skanowanie bajtów jest szybkie i nie wymaga silnika renderującego, ale ma wrodzoną ślepą plamę w kwestii czcionek: na tym poziomie program może jedynie zastosować prostą heurystykę — oznacza błąd tylko wtedy, gdy w pliku nie ma w ogóle żadnej osadzonej czcionki. Plik z dziewięcioma osadzonymi czcionkami i jedną systemową, która została dołączona bez osadzenia, przejdzie test na poziomie bajtów bez zastrzeżeń. Ta pojedyncza luka to powód, dla którego wprowadziliśmy drugi poziom walidacji

Sprawdzanie osadzenia czcionek za pomocą modelu obiektowego PDFium

Poziom modelu obiektowego w Komponencie PDFium precyzyjnie odpowiada na pytania o czcionki. Po przejściu testów bajtowych metoda TPdf.ValidatePdfX przechodzi przez każdą stronę, pobiera listę obiektów za pomocą FPDFPage_CountObjects, a dla każdego obiektu tekstowego pobiera uchwyt czcionki za pomocą FPDFTextObj_GetFont i bada go funkcją FPDFFont_GetIsEmbedded. Jedna nieosadzona czcionka w dowolnym miejscu dokumentu dodaje flagę pvxiPdfiumFontNotEmbedded do zbioru problemów. Praca walidatora kończy się natychmiast po wykryciu pierwszego błędu na stronie i przerywa dalsze ładowanie kolejnych stron, dzięki czemu dla uszkodzonego katalogu o objętości 300 stron werdykt zapada często już na pierwszej stronie

Warto wiedzieć o dwóch szczegółach. Po pierwsze, ten poziom walidacji wymaga załadowania biblioteki PDFium i wywołań eksportujących funkcję FPDFFont_GetIsEmbedded; gdy funkcja ta nie jest dostępna, test jest pomijany (nie kończy się błędem), więc starsze wersje bibliotek DLL nie będą generować fałszywych błędów. Po drugie, test sprawdza jedynie fakt osadzenia czcionki i nic więcej — nie rozróżnia pełnego osadzenia od subsetingu ani nie bada zestawu glifów. Jeśli plik nie przechodzi weryfikacji, a Ty chcesz wiedzieć, która czcionka na której stronie powoduje błąd, techniki opisane w artykule o analizie czcionek w Delphi za pomocą PDFium pozwolą Ci to ustalić

Walidacja strumieni bez ładowania dokumentu i biblioteki DLL

Weryfikator na poziomie bajtów jest również dostępny jako samodzielna funkcja ValidatePdfXCompliance(Source: TStream) w module FPdfPdfx. Jest to czysty kod w Object Pascalu, który nie wymaga biblioteki PDFium DLL. Pozwala to na wdrożenie go w miejscach, w których silnik renderujący nie jest pożądany — na przykład jako szybki walidator przy przesyłaniu plików na serwerze webowym, w zadaniach CI weryfikujących generowane grafiki czy w usługach Lazarus na platformach, na które nie chcemy dostarczać natywnych bibliotek DLL. Funkcję można zasilić dowolnym strumieniem z możliwością przewijania (seekable stream):

uses Classes, FPdfPdfx;

function QuickPdfXGate(const FileName: string): Boolean;
var
  Fs: TFileStream;
  Res: TPdfXValidationResult;
begin
  Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfXCompliance(Fs);
    Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
  finally
    Fs.Free;
  end;
end;

Kompensata jest jasna: samodzielna ścieżka wykonuje testy znaczników i wszystkie osiem testów zawartości, ale pomija analizę czcionek w PDFium, przez co ocena czcionek opiera się na ogólnej heurystyce. Dobrze zaprojektowana architektura używa funkcji ValidatePdfXCompliance jako szybkiego i taniego pierwszego testu, a pełną metodę TPdf.ValidatePdfX uruchamia tylko dla plików, które pomyślnie przeszły pierwszy etap

Gdzie kończy się ten walidator, a zaczyna pełny preflight

Funkcja ValidatePdfX weryfikuje znaczniki identyfikacyjne, zakazy strukturalne, geometrię stron, deklaracje Trapped oraz osadzanie czcionek w poszczególnych obiektach tekstowych. Nie mierzy jednak całkowitego nałożenia farb (TIC), nie bada poprawności przestrzeni kolorów (np. wymogu wyłącznie CMYK w standardzie X-1a), nie sprawdza rozdzielczości obrazów pod kątem rastra ani nie ocenia zachowania nadruku (overprint) i spłaszczania przezroczystości — do tego wymagany jest pełny silnik profilowania barwnego, a dokumentacja modułu wprost zaleca stosowanie go do ostatecznej certyfikacji. To, co daje dwupoziomowy test, to wykrycie 80% najczęstszych błędów strukturalnych w kilka milisekund wewnątrz Twojego kodu w Delphi, zamiast w e-mailu z drukarni odesłanym następnego dnia

Oba poziomy walidacji, API zapisu znaczników PDF/X do tworzenia zgodnych plików, a także walidatory standardów PDF/A, PDF/UA, PDF/E i PDF/VT oparte na tej samej architekturze, są dostarczane w ramach PDFium Component dla Delphi i C++Builder — jeden komponent od renderowania po prepressową weryfikację dokumentów