Articol tehnic

Documente de inginerie PDF/E-1 în Delphi cu PDFlibPas

PDF/E-1 e profilul de arhivare pentru documentele de inginerie, iar PDFlibPas îl implementează ca un mod de autor pe care îl activați cu SetPDFEMode plus un preflight cu limite care citește content stream-urile operator cu operator. Profilul nu e un PDF/A cu altă etichetă: are propriul namespace de identificare, propria cerință de metadate de ciclu de viață și o regulă care face validarea conținutului mai strictă decât în orice profil de arhivare întâlnit până acum

Livrabilele de inginerie sunt motivul pentru care există profilul. Un set de planșe care trebuie să fie lizibil și demonstrabil neschimbat peste douăzeci de ani, cu un istoric de revizii care supraviețuiește și cu o culoare care înseamnă același lucru pe plotter-ul din clădirea alăturată. Aceste cerințe produc o specificație ale cărei exigențe stau mai ales în afara conținutului paginii, în metadate și managementul culorii, exact locul în care un scriitor PDF generic greșește

Identificare proprie, nu o variațiune pe PDF/A

Primul lucru de înțeles corect e că identificarea PDF/E-1 nu poate fi produsă adaptând tiparul PDF/A sau PDF/X. Folosește un namespace XMP distinct, http://www.aim.org/pdfe/ns/id/, iar valoarea versiunii trebuie să apară în două locuri: ca intrare în informațiile documentului și ca proprietate XMP calificată cu namespace. Emisia doar a proprietății XMP, sau doar a intrării de informații, produce un fișier care poartă intenția și pică la validare

Output intent-ul are o formă la fel de specifică. PDF/E-1 cere un profil ICC încorporat cu identificatorul de subtip ISO_PDFE1, iar profilul trebuie să aibă un număr de componente care să se potrivească cu familia de culori de dispozitiv folosită efectiv de document. Clauza din urmă e locul în care implementările greșesc pe furiș, pentru că înseamnă că intent-ul nu poate fi ales dinainte și apoi ignorat

De ce are nevoie culoarea de dispozitiv de o străbatere a întregului document?

Pentru că spațiile de culoare se ascund în dicționare de resurse pe care o scanare la nivel de pagină nu le ajunge niciodată. PDF/E-1 tratează DeviceRGB și DeviceCMYK ca familii reciproc exclusive pentru un document, deci validarea profilului înseamnă să știi fiecare spațiu de culoare de dispozitiv folosit de orice din fișier. Un form XObject are resursele lui. La fel un pattern, la fel o imagine. Un tiling pattern într-un form XObject într-o pagină stă trei niveluri în adâncime, iar un validator care verifică doar resursele de vârf ale paginii va da drept bun un document care folosește ambele familii

Străbaterea înregistrează deci spațiile de culoare în timp ce parcurge pagini, forme, imagini și pattern-uri ca o singură traversare, și abia apoi decide dacă documentul e coerent și dacă output intent-ul se potrivește. Același raționament antrenează în general arhitectura preflight-ului: traversarea parțială produce treceri false, iar o trecere falsă la o verificare de conformitate e mai rea decât lipsa oricărei verificări, pentru că e înregistrată drept dovadă

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

    // Modul de autor ține metadatele de ciclu de viață la pas la fiecare salvare.
    // Întrebați înainte de salvare dacă documentul ar trece propria sa poartă
    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;

Metadatele de ciclu de viață sunt o obligație la fiecare salvare

PDF/E-1 cere mai mult decât un identificator de document. Setul minim include identificatorul de document de media management, un identificator de versiune, o clasă de redare, ora creării, ora modificării, ora metadatelor și un titlu. E un vocabular de urmărire a reviziilor, iar el există pentru că se așteaptă ca un livrabil de inginerie să fie reemis, nu scris o singură dată

Consecința pentru o implementare e că aceste câmpuri nu pot fi stabilite la crearea documentului. Dacă ora modificării se scrie când activați modul și documentul e editat după aceea, snapshot-ul XMP și starea reală a documentului au derivat, iar un validator care le compară raportează o inconsecvență pe care nimeni n-a intenționat-o. Modul de autor sincronizează deci câmpurile imediat înainte de fiecare salvare, astfel încât metadatele descriu octeții pe care urmează să-i scrie, nu octeții care existau când a fost activat modul

E un principiu general pentru metadatele de conformitate și merită enunțat separat de PDF/E: metadatele derivate aparțin pe calea de salvare, nu pe cea de editare. Orice câmp calculat din starea documentului trebuie recalculat în momentul în care starea îngheață, altfel e un cache fără invalidare

Diagramă PDFlibPas PDF/E-1 a străbaterii culorii de dispozitiv pe întregul document, care parcurge dicționarele de resurse ale paginilor, form XObject, tiling pattern și imagini colectând familiile DeviceRGB și DeviceCMYK înainte de a judeca coerența, alături de câmpurile de metadate de ciclu de viață pe care modul de autor le resincronizează imediat înainte de fiecare salvare, astfel încât snapshot-ul XMP să se potrivească cu octeții pe care urmează să-i scrie
Coerența culorii poate fi judecată doar după ce o singură traversare ajunge la fiecare dicționar de resurse, iar metadatele de ciclu de viață derivate se recalculează în momentul în care starea documentului îngheață, nu când se activează modul

Regula care face validarea conținutului strictă

PDF/E-1 nu permite operatorilor din secțiunea de compatibilitate să absoarbă conținut necunoscut. În PDF-ul obișnuit, BX și EX incadrează o regiune în care un consumator trebuie să ignore operatorii pe care nu-i recunoaște, adică ieșirea de salvare care lasă un producător să emită construcții mai noi fără să strice cititoarele mai vechi. Sub PDF/E-1 ieșirea aceea e închisă, deci orice operator pe care preflight-ul nu îl recunoaște e raportat necondiționat, indiferent dacă stă sau nu într-o secțiune de compatibilitate

Efectul asupra unui validator e semnificativ. Nu poate sări regiuni pe care nu le înțelege, ceea ce înseamnă că parser-ul de operanzi trebuie să parseze cu adevărat fiecare operator din fiecare content stream. Aici intră în scenă limitele. Traversarea e plafonată la 128 de niveluri de imbricare, un milion de obiecte și 64 MiB de conținut, iar aceste limite nu sunt reglaje de performanță. Un fișier ostil sau pur și simplu stricat poate prezenta un graf de obiecte cu cicluri sau o adâncime de imbricare care transformă un validator recursiv într-un stack overflow, iar limitele sunt cele care împiedică o trecere de validare să devină un vector de denial-of-service. Aceeași postură defensivă e descrisă în parsarea în siguranță a PDF-urilor neverificabile

// Validare de sine stătătoare a unui fișier pe care nu l-ați produs,
// fără a-l încărca într-o instanță de document
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;

Ce repară poarta de salvare și ce refuză

Poarta își împarte munca în două etape, iar împărțirea e o idee de design folositoare în sine. Mai întâi normalizează lucrurile reparabile în siguranță: fanioanele de tipărire ale adnotațiilor, fanioanele fără zoom și fără rotire ale adnotațiilor de text și fanionul de generare a aspectului din dicționarul de formular. Sunt setări cu o singură valoare corectă sub profil și fără conținut de informație, deci repararea lor tăcută e corectă, iar refuzul din cauza lor ar fi pedanterie

Apoi verifică constrângerile care nu pot fi reparate fără a schimba ce înseamnă documentul: versiunea, identificarea, criptarea, output intent-ul, coerența culorii de dispozitiv și prezența conținutului de formular dinamic. Un document care pică vreuna dintre ele e refuzat, pentru că a inventa un output intent sau a alege o familie de culori în locul autorului ar produce un fișier care trece validarea și înșală asupra conținutului

Diagramă a porții de salvare PDF/E-1 din PDFlibPas pentru Delphi, arătând preflight-ul cu limite care scanează fiecare operator din content stream sub plafoanele de 128 de niveluri de imbricare, un milion de obiecte și 64 MiB, repară tăcut fanioanele de tipărire, zoom și rotire ale adnotațiilor, refuză versiunea greșită, identificarea, criptarea, output intent-ul, culoarea de dispozitiv sau conținutul de formular dinamic și raportează blocajele prin GetPDFEDiagnostics
Poarta repară tăcut doar ce nu poartă informație, refuză fiecare constrângere pe care o reparare ar distorsiona și transformă refuzul într-o listă de blocaje prin GetPDFEDiagnostics înainte ca vreun octet să ajungă pe disc

Citirea diagnosticelor prin GetPDFEDiagnostics înainte de salvare transformă acel refuz într-o listă acționabilă, nu într-o operație eșuată. Într-un pipeline de lot, apelați-o pe fiecare document, înregistrați blocajele per fișier și dirijați eșecurile spre o coadă pe care o privește un om. E cu mult mai util decât o salvare care ridică excepție, pentru că blocajele tind să se adune: patruzeci de documente care pică din același output intent lipsă e o singură corecție, nu patruzeci

Alegerea dintre profilurile de arhivare

PDF/E-1 e ținta potrivită când livrabilul e documentație de inginerie cu un ciclu de viață al reviziilor și, mai ales, când coerența culorii de dispozitiv contează, pentru că ieșirea merge spre plottere și imprimante de format mare. PDF/A e ținta potrivită când scopul e lizibilitatea pe termen lung a documentelor în general și e profilul cu cel mai larg suport în validatoare. Cele două nu sunt interschimbabile, iar un document poate satisface unul și pică pe celălalt

Diagramă de decizie PDFlibPas care compară profilurile de arhivare PDF/E-1 și PDF/A pentru Delphi: PDF/E-1 pentru livrabile de inginerie cu cicluri de revizii, culoare de plotter și validare contractuală sub propriul namespace XMP cu output intent ISO_PDFE1, PDF/A pentru lizibilitate generală pe termen lung cu cel mai larg suport în validatoare
Plecați de la cine validează fișierul la capăt: profilurile cer garanții diferite de identificare, metadate și culoare, iar un document poate satisface unul eșuând pe celălalt

Dacă alegeți, plecați de la cine validează fișierul la capătul lanțului. Uneltele de validare PDF/A sunt peste tot, iar preflight-ul corespunzător din PDFlibPas e descris în preflight PDF/A și PDF/UA. Validarea PDF/E e mai specializată și e de regulă o cerință contractuală, nu un implicit. Când o arhivă existentă trebuie adusă la un profil pentru care nu a fost scrisă niciodată, calea de reparare a metadatelor din conversia în PDF/A cu reparare de metadate e tiparul de urmat, iar aceeași formă se aplică și aici: identificați, reparați ce e sigur, refuzați restul cu o listă

Modul de autor, preflight-ul de conținut cu limite și verificarea de conformitate de sine stătătoare vin toate cu PDFlibPas, biblioteca PDF pentru Delphi, deci un document poate fi produs sub profil și verificat independent după aceea printr-o cale de cod separată, ceea ce e singurul aranjament în care merită să aveți încredere pentru o pretenție de conformitate