Artykuł techniczny

Automatyzacja kontroli preflight PDF w Delphi z HotPDF

Plik otwiera się bez zarzutu na twojej maszynie. Acrobat go pokazuje, podgląd wydruku wygląda dobrze, wszystkie strony są na miejscu. Potem trafia do drukarni albo do systemu archiwum, który przyjmuje twoją miesięczną partię, i wraca odrzucony: obrazy RGB w zleceniu CMYK, brak klucza /Trapped, cel wyjściowy niepasujący do maszyny drukarskiej. Z dokumentem nie było nic nie tak, co ktokolwiek mógłby zobaczyć. Był niezgodny z profilem, a profil sprawdzono gdzieś, gdzie ciebie nie było. Preflight to prepressowa nazwa tego sprawdzenia, a prawdziwym pytaniem jest, gdzie ono należy, gdy pliki PDF wychodzą z twojego własnego kodu w Delphi, a nie z pulpitu projektanta

HotPDF nie daje ci funkcji preflight do wywołania. Komponent niesie okno raportu preflight w swoim demie z interfejsem, ale nie stoi za nim żadne API, które mogłaby wywołać usługa albo skrypt budowania, a udawanie inaczej wysłałoby cię na poszukiwanie metody, której nie ma. Brzmi to jak dziura, dopóki nie zauważysz, że dla plików, które sam generujesz, wywoływanie walidatora na własnym wyjściu i tak jest złym kształtem rozwiązania. Kontrolujesz już każdą właściwość, którą walidator by obejrzał. Użytecznym podziałem jest uczynienie generatora niezdolnym do wyemitowania złego pliku, a potem udowodnienie tego narzędziem, którego nie napisałeś

Diagram potoku preflight w Delphi, w którym ustawienia zgodności HotPDF zapobiegają powstaniu złych plików PDF podczas generowania, a veraPDF wraz z Acrobat Preflight dowodzą wyniku zewnętrznie
Zapobieganie wpieka reguły PDF/A i PDF/X w samo generowanie w HotPDF, a zewnętrzne walidatory dostarczają werdyktu, którego generator nie może wystawić sam sobie

Dlaczego własne wyjście sprawdza się inaczej

Tradycyjny preflight zakłada plik obcy. Wyprodukował go jakiś projektant, jakaś inna aplikacja, jakiś nieznany łańcuch edycji, a ty go badasz, bo nie masz pojęcia, co jest w środku. Dokument wyprodukowany przez twój kod nie jest obcy. Osadzenie czcionek, przestrzeń barw, cel wyjściowy, blok metadanych: o wszystkim tym twój program zdecydował kilka milisekund przed tym, jak plik trafił na dysk. Badanie go potem, by odkryć wybory, które przed chwilą sam podjąłeś, to praca dla samej pracy. Tańszym ruchem jest ograniczenie tych wyborów tak, by plik niezgodny nigdy nie zaistniał do wyłapania

Jest też powód wiarygodnościowy, by weryfikację trzymać na zewnątrz. Biblioteka błogosławiąca własne wyjście ocenia własny egzamin. Gdy system archiwum klienta albo RIP drukarni odrzuci twój plik, „nasz komponent mówi, że jest w porządku” nie waży nic. Werdykt veraPDF albo Acrobata waży, ponieważ druga strona uruchamia te same narzędzia

Uczyń zgodność ustawieniem, a nie listą kontrolną

Warstwa zapobiegania to zwykła konfiguracja. Ustaw PDFACompliance albo PDFXCompliance przed BeginDoc, a HotPDF trzyma odpowiadające reguły przez cały przebieg generowania: osadza czcionki, pilnuje użycia DeviceRGB i DeviceCMYK względem zadeklarowanego przez ciebie celu wyjściowego i odmawia funkcji zakazanych przez profil. Sprzeczności wychodzą na jaw przy EndDoc, gdzie bramki zgodności zgłaszają wyjątek zamiast po cichu wypuszczać coś, co zawiedzie dalej w łańcuchu. Gdy plik jest już zapisany, te same właściwości odczytują z powrotem to, co faktycznie wyegzekwowano, a to jest ten jeden fakt, którego twój log potoku potrzebuje najbardziej:

// Po EndDoc: zapisz wyegzekwowane profile wraz z metadanymi przebiegu
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Umieść te flagi w tej samej linii logu co skrót danych wejściowych i wersję HotPDF. W dniu, w którym walidator i twój generator nie zgodzą się co do pliku, ta linia powie ci, który szablon go wyprodukował i która kompilacja biblioteki była wczytana, a spór, który inaczej zjadłby popołudnie, zamienia się w grepa. Cele wyjściowe, profile ICC i oznaczanie stojące za tymi flagami są rozpisane w przewodniku po wyjściu PDF/A, PDF/X i PDF/UA z HotPDF

Tania pierwsza bramka dla plików, których nie wygenerowałeś

Nie każdy potok jest czysto generujący. Klienci wysyłają pliki PDF, skanery zrzucają je do folderu, partnerzy dołączają je do wiadomości. Przepychanie każdego z nich przez pełny walidator strukturalny marnuje czas kolejki na pliki, które nawet się nie otworzą. Direct File API w HotPDF odczytuje dość ze struktury pliku, by odpowiedzieć na pytanie „czy to w ogóle jest zdatny do użycia PDF”, bez wczytywania całego drzewa obiektów, co czyni je dobrym miejscem na szybkie odrzucenie:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // strukturalnie nieczytelny: kwarantanna, nie waliduj
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Dwa fakty o tym API decydują o tym, jak je opakujesz. Skrót płaskiej pamięci obowiązuje wyłącznie dla wejść niezaszyfrowanych; podaj DAOpenFileReadOnly hasło, a po cichu zejdzie do pełnego parsowania, więc plik, o którym wiesz, że jest zaszyfrowany, powinien przejść przez DecryptFile do zwykłej kopii roboczej przed segregacją. A DAGetPageCount nic nie znaczy na uchwycie, który nie otworzył się czysto, więc sprawdzenie uchwytu pozostaje ścisłe, a wynik niedodatni to odrzucenie, a nie ponowienie. Więcej takich wzorców mieszka w artykule o Direct File API dla procesów z dużymi plikami PDF

veraPDF uruchamiane jako część budowania

Dla wszystkiego, co deklarujesz jako PDF/A albo PDF/UA, veraPDF jest walidatorem, który należy wpiąć. Działa bez interfejsu, przyjmuje partię, emituje XML albo JSON i nazywa każde niepowodzenie klauzulą ISO, więc awaria reguły względem ISO 19005-1 klauzula 6.2.2 wskazuje prosto na ustawienie generatora, zamiast zostawiać cię ze zgadywaniem. Sterowanie nim z Delphi to zwykłe zarządzanie procesem:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // ogranicz czekanie na plik
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Ten limit czasu zarabia na siebie. Zniekształcony plik potrafi zapędzić dowolny parser w kąt, z którego już nie wychodzi, a bezterminowe czekanie wewnątrz procesu roboczego kolejki ciągnie resztę kolejki w dół razem ze sobą. Ogranicz czekanie, daj przekroczeniu limitu własny kod niepowodzenia i odłóż plik dla człowieka. Gdy czytasz wynik, parsuj XML pod kątem identyfikatorów reguł, a nie tekstu dla ludzi. Identyfikatory reguł przeżywają aktualizacje walidatora; brzmienie komunikatów nie, a stabilny kod to coś, po czym inżynier wsparcia może przeszukać stare zgłoszenia

To, jak uruchamiasz partię, znaczy tyle samo, co to, czy każdy plik przechodzi. Jeden proces na plik, a nie jeden na partię, żeby zatrute wejście kosztowało cię limit czasu tego jednego pliku i nic więcej. Ogranicz liczbę procesów walidatora do liczby rdzeni, bo budowanie raportu XML jest ograniczone procesorem, a przesubskrybowanie tylko młóci. I postaw sufit rozmiaru przy przyjmowaniu, bo dwugigabajtowa zeskanowana książka zawłaszczy kolejkę niezależnie od tego, jak cierpliwy jest parser. Nic z tego nie jest preflightem w ścisłym sensie. To różnica między bramką, która przeżyje wolumen końca miesiąca, a taką, którą wyłączą pierwszej nocy, gdy zatrzyma potok o drugiej nad ranem

Diagram wsadowej bramki w Delphi, która uruchamia jeden proces veraPDF na plik PDF w ramach ograniczonego limitu czasu, wydobywa z XML identyfikatory reguł zamiast komunikatów i archiwizuje każdy raport obok jego pliku
Strażnik przyjmowania ogranicza obciążenie kolejki, a jeden świeży proces roboczy veraPDF na plik nie pozwala zatrutemu wejściu zatrzymać budowania

PDF/X jest miejscem, w którym to nie wystarcza. veraPDF go nie waliduje, więc działającym sprawdzeniem pozostaje Preflight w Acrobacie z profilem ISO 15930 wskazanym przez twoją drukarnię. Acrobat chce człowieka, co oznacza próbkowanie zamiast pełnego pokrycia: pierwszy plik z nowego szablonu plus małe losowe wyciągnięcie z każdej partii, podczas gdy automatyczna bramka obsługuje wszystko, co da się obsłużyć bez człowieka. Próbkowane sprawdzenie, które naprawdę działa, bije kompletną automatyzację, która na zawsze zostaje w połowie

Raport, którego zechcesz jeszcze za rok

Bramka preflight opłaca się dwukrotnie. Raz, gdy zatrzyma zły plik przy drzwiach, i drugi raz znacznie później, gdy ktoś zapyta, dlaczego dany plik został przepuszczony. Ta druga chwila powinna dyktować format, bo to w niej cienki raport zostawia cię na lodzie. Dla każdego sprawdzonego pliku zachowaj skrót wejścia, flagi zgodności generatora i wersję biblioteki z linii logu powyżej, nazwę i wersję walidatora, profil, względem którego sprawdzano, wynik przejścia albo niepowodzenia oraz identyfikatory reguł, które zawiodły, wraz z numerami stron wszędzie tam, gdzie walidator je podaje. Przechowuj ten raport obok pliku, który opisuje. Umieść go w osobnym systemie, a ten system zostanie wyłączony wcześniej niż archiwum, które dokumentuje

Wyjątki też trzeba zapisywać. Gdy klient upiera się przy wysłaniu pliku, którego bramka nie lubi, odpowiedzią nie jest rozluźnienie reguły dla wszystkich. Zapisz, kto zatwierdził ten plik, na jakiej podstawie i do jakiej daty, a potem dołącz to odstępstwo do jego raportu. Odstępstwo z nazwiskiem i terminem ważności to decyzja, którą ktoś posiada. Sprawdzenie zakomentowane „tymczasowo” to incydent czekający na swoją datę

Jeszcze jeden nawyk sam się spłaca: gdy plik nie przejdzie, skopiuj go do nazwanego folderu regresyjnego, zanim ktokolwiek go dotknie. Niemal każdy problem z preflightem wart debugowania sprowadza się do jednego konkretnego wejścia, a zespoły, które te wejścia zachowują, naprawiają nawrót w godzinę zamiast czekać, aż wypłynie na produkcji. Pokazane tu właściwości zgodności i Direct File API są częścią komponentu HotPDF dla Delphi oraz C++Builder, którego dokumentacja opisuje każde wywołanie w całości