PDFlibPas atașează un fișier încorporat unei pagini anume, nu documentului luat ca întreg, scriind un vector /AF în dicționarul paginii, în timp ce conținutul propriu-zis rămâne înregistrat în arborele de nume EmbeddedFiles al documentului. Această împărțire e exact ceea ce descrie ISO 32000-2 §14.13 și exact ceea ce îi permite unui vizualizator să răspundă la întrebarea la care un atașament la nivel de document nu poate răspunde: a cărei pagină aparțin aceste date
Cazurile de utilizare sunt mai specifice decât la atașamentele generale. Un raport de măsurători în care fiecare pagină poartă seria brută de măsurători de la care vine graficul ei. Un lot scanat în care fiecare pagină păstrează rezultatul OCR care i-a produs stratul de text. Un set de planșe în care fiecare foaie poartă extragerea CAD din care a fost randată. În fiecare caz, o listă de atașamente la nivel de document ar fi o grămadă de fișiere cu numere de pagină ascunse în nume, adică o convenție, nu o structură
Un singur conținut, două locuri de unde e referențiat
Punctul structural important e că asocierea la nivel de pagină nu creează nicio a doua copie. Fișierul e încorporat o singură dată și înregistrat în arborele de nume EmbeddedFiles exact ca un atașament la nivel de document, cu aceeași mașinărie de specificație de fișier. Ce diferă e locul în care se scriu referința și cheia ei de relație: în dicționarul paginii, nu în catalogul documentului
Urmează două consecințe. În primul rând, un vizualizator care cunoaște doar atașamentele la nivel de document găsește în continuare conținutul, pentru că el stă în arborele de nume pe care un asemenea vizualizator îl parcurge. În al doilea rând, ștergerea asocierii paginii înlătură legătura, nu fișierul. ClearPageAssociatedFiles detașează pagina de fișierele ei asociate și lasă conținuturile accesibile prin arborele de nume, ceea ce e comportamentul conservator: o operație care spune „șterge asocierea” nu ar trebui să distrugă tăcut date la care se poate referi o altă parte a documentului
Funcția aceea are o condiție de succes deliberat îngustă, care merită cunoscută. Raportează succes doar când pagina chiar a purtat o cheie /AF. O pagină care nu a avut niciodată asocieri returnează eșec, nu o confirmare bucuroasă, deci apelantul nu poate confunda un no-op cu o curățenie dusă la capăt
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Atașează seria de măsurători care a produs graficul de pe pagina 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // fișier pe disc
'measurements.csv', // nume afișat în interiorul PDF-ului
'text/csv', // tip MIME
'Raw measurement series for figure 3',
'Data'); // AFRelationship, ISO 32000-2 14.13
if Idx < 0 then
raise Exception.Create('page association refused');
for I := 0 to Lib.GetPageAssociatedFileCount(3) - 1 do
Writeln('page 3 associated file, embedded index ',
Lib.GetPageAssociatedFileEmbeddedIndex(3, I));
Lib.SaveToFile('survey-report-with-data.pdf');
finally
Lib.Free;
end;
end;
Șirul de relație nu e text liber în practică. ISO 32000-2 definește un vocabular, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema și Unspecified, iar consumatorii se ancorează în el. Data pentru cifrele de dincolo de un grafic, Source pentru documentul din care a fost generată o pagină, Alternative pentru o reprezentare echivalentă. Alegeți din vocabular chiar dacă nimic din pipeline-ul dumneavoastră nu îl citește încă, pentru că următoarea unealtă din lanț s-ar putea să o facă
De ce are nevoie aceeași căutare de FollowRef în ambele sensuri?
Pentru că urmărirea referințelor răspunde la două întrebări diferite, iar codul trebuie să știe pe care o pune. O căutare după cheie care urmărește referințele indirecte întoarce obiectul la care punctează referința. O căutare care nu urmărește întoarce referința în sine. Ambele sunt corecte, iar folosirea celei greșite produce un comportament greșit tăcut, nu o eroare
Citirea unui fișier asociat demonstrează primul sens. Pentru a obține numărul de obiect al stream-ului încorporat de după cheile /EF și /F ale specificației de fișier, căutarea trebuie să nu urmărească, pentru că urmărirea rezolvă referința în obiectul stream și numărul de obiect se pierde. Regula se generalizează: orice cale de cod care are nevoie de identitatea unui obiect, nu de conținutul lui, trebuie să ia referința brută
Conținutul opțional arată sensul opus, iar descoperirea lui a costat mai mult. Dicționarul de proprietăți al conținutului opțional e scris în catalog ca obiect indirect, deci codul care îl citește înapoi fără urmărire primește o referință, nu un dicționar. O verificare de tip pe valoarea aceea eșuează apoi, și se pornește ramura naturală de rezervă — dacă nu există configurație, creează una — care suprascrie configurația care era deja acolo. Nimic nu ridică excepții. Layerele descrise în grupurile de conținut opțional și layerele își pierd pur și simplu starea implicită de vizibilitate
Lecția se generalizează dincolo de ambele cazuri. Când o căutare poate întoarce fie o referință, fie obiectul, o verificare de tip nudă nu este tratare de erori: e o ramură care va fi luată, la un moment dat, din motivele greșite. Decideți explicit ce are nevoie fiecare loc de apel și preferați API-ul public care răspunde direct la întrebare, de pildă o proprietate de numărare a conținutului opțional, în loc să vă întindeți spre un accesor protejat pentru dicționarul catalogului
// Atașamentele la nivel de document și asocierile la nivel de pagină coexistă.
// Un fișier încorporat poate fi marcat asociat și la nivel de document
if Lib.IsEmbeddedFileAssociated(0) = 0 then
Lib.SetEmbeddedFileAssociated(0, 1, 'Supplement');
Writeln('document associated files: ', Lib.GetAssociatedFileCount);
Writeln('page 3 associated files : ',
Lib.GetPageAssociatedFileCount(3));
// Ștergerea detașează legătura de pagină; conținutul rămâne în arborele de nume
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Ce fac modurile de conformitate cu atașamentele
Profilurile de arhivare restricționează ce poate fi încorporat, iar restricția e aplicată la punctul de intrare, nu la momentul salvării. PDF/A-1 interzice complet fișierele încorporate, PDF/A-2 permite doar documente PDF/A încorporate, iar PDF/A-3 e profilul care a deschis încorporarea către tipuri de fișiere arbitrare, tocmai de aceea formatele hibride de factură se construiesc pe el
PDFlibPas refuză atașamentul când modul de conformitate activ nu îl permite, la apel, nu sute de operații mai târziu, la ieșire. E o alegere deliberată despre unde e o eroare cea mai ieftin de tratat: un refuz la locul apelului numește fișierul pe care tocmai îl adăugai, pe când un refuz la salvare numește un document și vă lasă pe dumneavoastră să stabiliți care dintre cele patruzeci de atașamente l-a provocat
Așa se face că fișierele asociate apar atât de des în facturarea electronică. O factură hibridă e un PDF pe care îl citește un om, cu un conținut XML lizibil de mașină atașat și marcat cu relația potrivită, iar atât profilul containerului, cât și cheia de relație fac parte din specificație, nu din convenții. Această construcție e acoperită în construirea facturilor hibride Factur-X și ZUGFeRD, cu partea de metadate în schema de extensie XMP din PDF/A-3
Când trebuie asocierea să fie pe pagină, nu pe document?
Când un consumator are nevoie să știe cărei pagini îi aparțin datele, și doar atunci. Atașamentele la nivel de document sunt mai simple, mai susținute de vizualizatoare și suficiente ori de câte ori conținutul descrie întregul document: un XML de factură, un manifest de semnătură, o arhivă cu surse. Recurgeți la asocierea la nivel de pagină când conținutul e cu adevărat delimitat pe pagină și identitatea paginii face parte din sensul lui
Suportul e constrângerea practică. Fișierele asociate la nivel de pagină sunt o construcție PDF 2.0, iar suportul în vizualizatoare e mai subțire decât pentru atașamentele la nivel de document. Pentru că conținutul stă oricum în arborele de nume, un vizualizator care ignoră /AF pe pagini arată în continuare fișierul în lista de atașamente, deci degradarea e grațioasă. Dar dacă legătura de pagină e esențială pentru consumatorul dumneavoastră, nu doar metadate utile, verificați vizualizatorul pe care îl țintiți efectiv, în loc să presupuneți
Fișierele asociate la nivel de pagină, atașamentele la nivel de document și porțile profilurilor de arhivare care le guvernează pe ambele vin cu PDFlibPas, biblioteca PDF pentru Delphi. Dacă reparați și fișiere mai vechi pe parcurs, lucrarea de metadate și conformitate din conversia în PDF/A cu reparare de metadate e cea care stabilește, de la început, care dintre aceste rute de atașare vă stau la dispoziție