Technický článek

Inženýrské dokumenty PDF/E-1 v Delphi s PDFlibPas

PDF/E-1 je archivační profil pro inženýrské dokumenty a PDFlibPas ho implementuje jako author mode, který zapnete přes SetPDFEMode, plus ohraničený preflight čtoucí content streamy operátor po operátoru. Tenhle profil není PDF/A s jiným štítkem: má vlastní identifikační namespace, vlastní požadavek na lifecycle metadata a jedno pravidlo, které dělá validaci obsahu přísnější než u jakéhokoli archivačního profilu, který jste potkali

Inženýrské výstupy jsou důvod existence toho profilu. Sada výkresů, která musí být za dvacet let čitelná a prokázatelně nezměněná, s historií revizí, která přežije, a s barvami, které znamenají totéž i na plotru v vedlejší budově. Tahle požadavky tvoří specifikaci, jejíž nároky sedí většinou mimo obsah stránek, v metadatech a správě barev, což je přesně tam, kde se generický PDF writer splete

Vlastní identifikace, ne variace na PDF/A

První věcí, kterou je potřeba trefit správně, je to, že identifikaci PDF/E-1 nelze vyrobit úpravou vzoru z PDF/A nebo PDF/X. Používá odlišný XMP namespace, http://www.aim.org/pdfe/ns/id/, a hodnota verze se musí objevit na dvou místech: jako položka document information a jako XMP property kvalifikovaná namespace. Vyslat jen XMP property, nebo jen položku information, produkuje soubor, který sice záměr nese, ale validací neprojde

Output intent má stejně specifickou podobu. PDF/E-1 vyžaduje vložený ICC profil s podtypovým identifikátorem ISO_PDFE1 a profil musí mít počet komponent odpovídající rodině device barev, kterou dokument skutečně používá. Právě ta poslední klauzule je místo, kde se implementace potichu spletou, protože to znamená, že intent nelze vybrat předem a pak ignorovat

Proč potřebuje device barva průchod celým dokumentem?

Protože barevné prostory se schovávají ve slovnících resources, kam sken na úrovni stránky nikdy nedosáhne. PDF/E-1 bere DeviceRGB a DeviceCMYK jako u dokumentu navzájem výlučné rodiny, takže validace profilu znamená znát každou device barevnou prostor, kterou cokoli v souboru používá. Form XObject má vlastní resources. Pattern taky, image taky. Tiling pattern uvnitř form XObjectu uvnitř stránky je tři úrovně hluboko a validátor kontrolující jen resources nejvyšší úrovně stránky pustí dokument používající obě rodiny

Průchod proto registruje barevné prostory při procházení stránek, formulářů, obrázků a patternů jako jediný traversal, a teprve potom rozhodne, zda je dokument koherentní a zda output intent odpovídá. Táž úvaha řídí obecně celou architekturu preflightu: částečný traversal produkuje falešné průchody a falešný průchod u konformační kontroly je horší než žádná kontrola, protože se zaeviduje jako důkaz

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

    // Author mode udržuje lifecycle metadata v kroku při každém uložení.
    // Před uložením se zeptej, zda by dokument prošel vlastní branou
    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;

Lifecycle metadata je povinnost při každém uložení

PDF/E-1 chce víc než identifikátor dokumentu. Minimální sada zahrnuje identifikátor dokumentu media management, identifikátor verze, rendition class, čas vytvoření, čas úpravy, čas metadat a název. To je slovník na sledování revizí a existuje proto, že se od inženýrského výstupu čeká vydávání nových verzí, ne jednorázové napsání

Důsledek pro implementaci je, že tyhle pole nelze nastavit při vytvoření dokumentu. Jestliže se čas úpravy zapíše ve chvíli, kdy zapnete mód, a dokument se potom upraví, XMP snapshot a skutečný stav dokumentu se rozejdou a validátor je porovnávající nahlásí nekonzistenci, kterou nikdo nezamýšlel. Author mode proto synchronizuje pole těsně před každým uložením, takže metadata popisují bajty, které se teprve chystají zapsat, ne bajty, které existovaly, když se mód zapínal

To je obecný princip konformačních metadat a stojí za to ho vyložit zvlášť od PDF/E: odvozená metadata patří na save path, ne na edit path. Jakékoli pole počítané ze stavu dokumentu se musí přepočítat v okamžiku, kdy se stav zmrazí, jinak je to cache bez invalidace

Diagram PDF/E-1 v PDFlibPas: průchod device barvami celým dokumentem, který obchází slovníky resources stránek, form XObjectů, tiling patternů a obrázků a sbírá rodiny DeviceRGB a DeviceCMYK, než posoudí koherenci, vedle polí lifecycle metadat, jež author mode resynchronizuje těsně před každým uložením, takže XMP snapshot odpovídá bajtům, které se teprve chystají zapsat
Barvní koherenci lze posoudit jen po jednom traversalu dosáhnuvším každého slovníku resources a odvozená lifecycle metadata se přepočítávají v okamžiku zmrazení stavu dokumentu, nikoli při zapnutí módu

Pravidlo, které dělá validaci obsahu přísnou

PDF/E-1 nedovoluje, aby operátory kompatibilní sekce pohltily neznámý obsah. V obyčejném PDF ohraničují BX a EX region, v němž musí konzument ignorovat operátory, které nepozná, což je escape hatch umožňující produkcentovi vyslat novější konstrukce, aniž rozbije starší čtečky. Pod PDF/E-1 je ta únikovka zavřená, takže každý operátor, který preflight nepozná, se hlásí bezpodmínečně, ať už sedí uvnitř kompatibilní sekce, nebo ne

Na validátor to má výrazný efekt. Nemůže přeskočit regiony, kterým nerozumí, což znamená, že parser operandů musí skutečně sparsovat každý operátor v každém content streamu. Právě tady přicházejí na řadu limity. Traversal je stropovaný na 128 úrovní zanoření, milion objektů a 64 MiB obsahu, a ty limity nejsou ladění výkonu. Nepřátelský, nebo jen rozbitý soubor může předložit graf objektů s cykly nebo zanořením tak hlubokým, že z rekurzivního validátoru udělá stack overflow, a limity jsou to, co brání validačnímu průchodu stát se vektorem denial-of-service. Tentýž defenzivní postoj rozebírá článek bezpečné parsování nedůvěryhodných PDF

// Samostatná validace souboru, který jste nevyrobili, bez načítání
// do instance 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 save brána opraví a co odmítne

Brána dělí svou práci na dvě etapy a tohle rozdělení je samo o sobě použitelný designový nápad. Nejdřív normalizuje věci bezpečně opravitelné: print flagy anotací, flagy no-zoom a no-rotate na textových anotacích a flag generování appearance ve slovníku formuláře. Tohle jsou nastavení s jedinou správnou hodnotou pod profilem a bez obsahu informace, takže jejich tichá oprava je správná a odmítat nad nimi by byla pedantárna

Potom kontroluje omezení, jež nelze opravit, aniž se změní to, co dokument znamená: verzi, identifikaci, šifrování, output intent, koherenci device barev a přítomnost dynamického obsahu formulářů. Dokument, který u jakéhokoli z nich propadne, je odmítnut, protože vymyslet output intent nebo vybrat rodinu barev za autora by vyprodukovalo soubor, který validací projde a obsah deformuje

Diagram save brány PDF/E-1 v PDFlibPas pro Delphi: ohraničený preflight skenující každý operátor content streamu pod stropy 128 úrovní zanoření, milion objektů a 64 MiB, potichu opraví print, zoom a rotate flagy anotací, odmítne špatnou verzi, identifikaci, šifrování, output intent, device barvy nebo dynamický obsah formulářů a blockery hlásí přes GetPDFEDiagnostics
Brána potichu opraví jen to, co nenese informaci, odmítne každé omezení, jež by oprava zdeformovala, a odmítnutí promění v seznam blockerů přes GetPDFEDiagnostics, než první bajty dorazí na disk

Přečtení diagnostiky zpět přes GetPDFEDiagnostics před uložením promění tohle odmítnutí v akční seznam místo neúspěšné operace. V batch pipeline ji volejte na každý dokument, logujte blockery per soubor a selhání směrujte do fronty, kterou si člověk projde. To je k užitku mnohem víc než save, který vyhodí výjimku, protože blockery se obvykle shlukují: čtyřicet dokumentů propadajících se stejným chybějícím output intentem je jedna oprava, ne čtyřicet

Volba mezi archivačními profily

PDF/E-1 je správný cíl, když je výstupem inženýrská dokumentace s revizním lifecycle a konkrétně tehdy, když záleží na koherenci device barev, protože výstup jde na plotry a velkoformátové tiskárny. PDF/A je správný cíl, když je cílem dlouhodobá čitelnost dokumentů obecně, a je to profil s nejširší podporou validátorů. Nejsou zaměnitelné a dokument může splnit jeden a propadnout druhém

Rozhodovací diagram PDFlibPas porovnávající archivační profily PDF/E-1 a PDF/A pro Delphi: PDF/E-1 pro inženýrské výstupy s revizním lifecycle, barvou na plotru a smluvní validací pod vlastním XMP namespace s output intentem ISO_PDFE1, PDF/A pro obecnou dlouhodobou čitelnost s nejširší podporou validátorů
Začněte u toho, kdo soubor na druhém konci validuje: profily vyžadují různou identifikaci, metadata a barevní garance a dokument může splnit jeden a propadnout druhém

Když si vybíráte, začněte u toho, kdo soubor na konci cesty validuje. Nástroje na validaci PDF/A jsou všude a odpovídající preflight v PDFlibPas popisuje článek preflight PDF/A a PDF/UA. Validace PDF/E je specializovanější a obvykle jde o smluvní požadavek, ne o výchozí stav. Když musí být stávající archiv dotažen na profil, pro který nikdy nebyl psán, vzorem je cesta opravy metadat v článku konverze na PDF/A s opravou metadat a platí tentýž tvar i tady: identifikuj, oprav, co je bezpečné, zbytek odmítni se seznamem

Author mode, ohraničený preflight obsahu i samostatná compliance kontrola přicházejí s PDFlibPas Delphi PDF knihovnou, takže dokument lze vyrobit pod profilem a následně nezávisle ověřit oddělenou kódovou cestou, což je jediné uspořádání, kterému u konformačního tvrzení stojí za důvěru