Artykuł techniczny

Dokumenty inżynieryjne PDF/E-1 w Delphi z PDFlibPas

PDF/E-1 to profil archiwalny dla dokumentów inżynieryjnych, a PDFlibPas implementuje go jako tryb autora, który włączasz przez SetPDFEMode, plus ograniczony preflight czytający strumienie zawartości operator po operatorze. Ten profil to nie PDF/A z inną etykietą: ma własną przestrzeń nazw identyfikacyjną, własne wymaganie metadanych cyklu życia i jedną regułę, która robi walidację zawartości ostrzejszą niż w jakimkolwiek profilu archiwalnym, jaki dotąd spotkałeś

Dokumentacja inżynieryjna to powód istnienia tego profilu. Zestaw rysunków, który za dwadzieścia lat ma być czytelny i dający się udowodnić jako niezmieniony, z historią rewizji, która przetrwa, i z kolorem, który na ploterze w innym budynku znaczy to samo. Te wymagania rodzą specyfikację, której żądania siedzą głównie poza zawartością stron, w metadanych i zarządzaniu kolorem, czyli dokładnie tam, gdzie generyczny pisarz PDF-ów je gubi

Własna identyfikacja, nie wariacja na temat PDF/A

Pierwsza rzecz do zrobienia dobrze: identyfikacji PDF/E-1 nie da się wyprodukować przez zaadaptowanie wzorca z PDF/A albo PDF/X. Używa odrębnej przestrzeni nazw XMP, http://www.aim.org/pdfe/ns/id/, a wartość wersji musi wystąpić w dwóch miejscach: jako wpis w informacji o dokumencie i jako kwalifikowana przestrzenią nazw właściwość XMP. Emitowanie samej właściwości XMP albo samego wpisu daje plik, który niesie zamiar, ale walidacji nie przechodzi

Output intent ma równie konkretny kształt. PDF/E-1 wymaga osadzonego profilu ICC z identyfikatorem podtypu ISO_PDFE1, a profil musi mieć liczbę składowych pasującą do rodziny kolorów urządzeniowych, której dokument faktycznie używa. To ostatnie zastrzeżenie to miejsce, gdzie implementacje po cichu się mylą, bo oznacza ono, że intent nie może być wybrany z góry i potem zignorowany

Dlaczego kolor urządzeniowy wymaga przeglądu całego dokumentu?

Bo przestrzenie kolorów kryją się w słownikach zasobów, do których skan poziomu strony nigdy nie zagląda. PDF/E-1 traktuje DeviceRGB i DeviceCMYK jako wzajemnie wykluczające się rodziny w obrębie dokumentu, więc walidacja profilu oznacza znajomość każdej urządzeniowej przestrzeni kolorów, z czego cokolwiek w pliku korzysta. Form XObject ma własne zasoby. Pattern też, i obraz też. Tiling pattern wewnątrz form XObject wewnątrz strony leży trzy poziomy w głąb, a walidator sprawdzający tylko zasoby strony najwyższego poziomu przepuści dokument używający obu rodzin

Przegląd rejestruje więc przestrzenie kolorów w czasie jednego przejścia po stronach, formularzach, obrazach i patternach i dopiero potem rozstrzyga, czy dokument jest spójny i czy output intent pasuje. To samo rozumowanie napędza całą architekturę preflightu: częściowe przejście produkuje fałszywe zaliczenia, a fałszywe zaliczenie kontroli zgodności jest gorsze niż jej brak, bo zostaje zapisane jako dowód

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');

    // Tryb autora trzyma metadane cyklu życia w ryzach przy każdym zapisie.
    // Zapytaj przed zapisem, czy dokument przeszedłby własną bramkę
    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;

Metadane cyklu życia to obowiązek przy każdym zapisie

PDF/E-1 żąda więcej niż identyfikatora dokumentu. Minimalny zestaw obejmuje identyfikator dokumentu z zarządzania mediami, identyfikator wersji, klasę rendition, czas utworzenia, czas modyfikacji, czas metadanych i tytuł. To słownictwo śledzenia rewizji i istnieje, bo od dokumentacji inżynieryjnej oczekuje się ponownych wydań, a nie napisania raz na zawsze

Konsekwencja dla implementacji: tych pól nie da się ustawić przy tworzeniu dokumentu. Jeśli czas modyfikacji zostanie wpisany w momencie włączenia trybu, a dokument jest potem edytowany, migawka XMP i rzeczywisty stan dokumentu rozjadą się, a walidator porównujący oba zgłosi niespójność, której nikt nie zamierzał. Tryb autora synchronizuje więc pola bezpośrednio przed każdym zapisem, dzięki czemu metadane opisują bajty, które właśnie trafią na dysk, a nie te, które istniały w chwili włączania trybu

To ogólna zasada dla metadanych zgodności i warto ją wypowiedzieć osobno od PDF/E: metadane wyliczane należą na ścieżkę zapisu, nie na ścieżkę edycji. Każde pole liczone ze stanu dokumentu musi być przeliczone w momencie zamrożenia stanu, inaczej jest to cache bez inwalidacji

Diagram PDFlibPas dla PDF/E-1 pokazujący urządzeniowy przegląd kolorów całego dokumentu, który przechodzi po słownikach zasobów stron, form XObject, tiling patternów i obrazów, zbierając rodziny DeviceRGB i DeviceCMYK, zanim oceni spójność, obok pól metadanych cyklu życia, które tryb autora resynchronizuje bezpośrednio przed każdym zapisem, tak by migawka XMP pasowała do bajtów tuż przed zapisem
Spójność kolorów da się ocenić dopiero, gdy jedno przejście dotrze do każdego słownika zasobów, a wyliczane metadane cyklu życia są przeliczane w momencie zamrożenia stanu dokumentu, nie przy włączaniu trybu

Reguła, która robi walidację zawartości ostrą

PDF/E-1 nie pozwala, by operatory sekcji zgodności wchłaniały nieznaną zawartość. W zwykłym PDF-ie BX i EX wyznaczają obszar, w którym konsument ma ignorować operatory, których nie rozpoznaje — to właz ewakuacyjny pozwalający producentowi emitować nowsze konstrukcje bez łamania starszych czytników. Pod PDF/E-1 ten właz jest zamknięty, więc każdy operator nierozpoznany przez preflight jest zgłaszany bezwarunkowo, niezależnie od tego, czy siedzi w sekcji zgodności

Efekt na walidator jest poważny. Nie może pomijać regionów, których nie rozumie, co znaczy, że parser operandów musi naprawdę sparsować każdy operator w każdym strumieniu zawartości. Tu wchodzą na scenę limity. Przejście jest capowane na 128 poziomach zagnieżdżenia, milionie obiektów i 64 MiB zawartości, i te limity to nie strojenie wydajności. Wrogi albo po prostu uszkodzony plik potrafi podać graf obiektów z cyklami albo z głębokością zagnieżdżenia, która zamienia rekurencyjny walidator w przepełnienie stosu, i to limity pilnują, by przebieg walidacji nie stał się wektorem denial-of-service. Tę samą postawę obronną opisuje bezpieczne parsowanie niezaufanych PDF-ów

// Samodzielna walidacja pliku, którego nie produkowałeś, bez ładowania
// go do instancji dokumentu
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;

Co bramka zapisu naprawia, a czego odmawia

Bramka dzieli swoją pracę na dwa etapy, a ten podział to sam w sobie użyteczny pomysł projektowy. Najpierw normalizuje to, co da się bezpiecznie naprawić: flagi drukowania adnotacji, flagi no-zoom i no-rotate na adnotacjach tekstowych oraz flagę generowania wyglądu w słowniku formularza. To ustawienia z jedną poprawną wartością pod tym profilem i bez żadnej zawartości informacyjnej, więc ciche poprawienie jest właściwe, a odmowa byłaby pedanterią

Potem sprawdza ograniczenia, których nie da się naprawić bez zmiany znaczenia dokumentu: wersję, identyfikację, szyfrowanie, output intent, spójność kolorów urządzeniowych i obecność dynamicznej zawartości formularzy. Dokument obalający choć jedno z nich zostaje odrzucony, bo wymyślenie output intentu czy dobranie rodziny kolorów w imieniu autora dałoby plik, który przechodzi walidację, a zawartość fałszywie prezentuje

Diagram bramki zapisu PDF/E-1 w PDFlibPas dla Delphi pokazujący ograniczony preflight skanujący każdy operator strumienia zawartości pod limitami 128 poziomów zagnieżdżenia, miliona obiektów i 64 MiB, cicho naprawiający flagi drukowania, zoomu i obrotu adnotacji, odrzucający złą wersję, identyfikację, szyfrowanie, output intent, kolor urządzeniowy lub dynamiczną zawartość formularzy i zgłaszający blokery przez GetPDFEDiagnostics
Bramka naprawia po cichu wyłącznie to, co nie niesie informacji, odrzuca każde ograniczenie, które naprawa by zniekształciła, i zamienia odmowę w listę blockerów przez GetPDFEDiagnostics, zanim jakikolwiek bajt trafi na dysk

Odczyt diagnostyki przez GetPDFEDiagnostics przed zapisem zamienia tę odmowę w listę działań zamiast nieudanej operacji. W potoku wsadowym wołaj ją na każdym dokumencie, loguj blockery per plik i kieruj porażki do kolejki, którą przegląda człowiek. To znacznie pożyteczniejsze niż zapis rzucający wyjątkiem, bo blockery zwykle kłębią się tematycznie: czterdzieści dokumentów obalonych przez ten sam brakujący output intent to jedna poprawka, nie czterdzieści

Wybór między profilami archiwalnymi

PDF/E-1 to właściwy target, gdy dostarczasz dokumentację inżynieryjną z cyklem rewizji, a konkretnie wtedy, gdy spójność kolorów urządzeniowych ma znaczenie, bo wydruk idzie na plotery i drukarki wielkoformatowe. PDF/A to właściwy target, gdy celem jest długoterminowa czytelność dokumentów w ogóle, i to profil z najszerszym wsparciem walidatorów. Te dwa nie są wymienne i dokument może spełniać jeden, a oblać drugi

Diagram decyzyjny PDFlibPas porównujący profile archiwalne PDF/E-1 i PDF/A dla Delphi: PDF/E-1 dla dostaw inżynieryjnych z cyklem rewizji, kolorem ploterowym i walidacją kontraktową we własnej przestrzeni nazw XMP z output intentem ISO_PDFE1, PDF/A dla ogólnej długoterminowej czytelności z najszerszym wsparciem walidatorów
Zacznij od tego, kto waliduje plik na końcu łańcucha: profile wymagają różnej identyfikacji, metadanych i gwarancji kolorystycznych, a dokument może spełniać jeden, obalając drugi

Jeśli wybierasz, zacznij od tego, kto waliduje plik na końcu łańcucha. Narzędzia walidacji PDF/A są wszędzie, a odpowiadający im preflight w PDFlibPas jest opisany w artykule preflight PDF/A i PDF/UA. Walidacja PDF/E jest bardziej specjalistyczna i zwykle jest wymaganiem kontraktowym, a nie domyślnym. Gdy istniejące archiwum trzeba doprowadzić do profilu, dla którego nigdy nie było pisane, wzorcem do naśladowania jest ścieżka naprawy metadanych z konwersji do PDF/A z naprawą metadanych, i ten sam kształt działa tutaj: zidentyfikuj, napraw to, co bezpieczne, resztę odrzuć z listą

Tryb autora, ograniczony preflight zawartości i samodzielna kontrola zgodności są wszystkie częścią biblioteki PDF PDFlibPas dla Delphi, więc dokument można wyprodukować pod profilem, a potem niezależnie zweryfikować osobną ścieżką kodu — i to jest jedyny układ, któremu przy twierdzeniu o zgodności warto ufać