PDFlibPas aggancia un file incorporato a una pagina specifica invece che al documento nel suo complesso, scrivendo una matrice /AF nel dizionario di pagina mentre il payload resta registrato nel name tree EmbeddedFiles del documento. È questa separazione che ISO 32000-2 §14.13 descrive, ed è ciò che permette a un reader di rispondere alla domanda a cui un allegato a livello di documento non sa rispondere: a quale pagina appartengono questi dati
I casi d'uso sono più specifici degli allegati generici. Un rapporto di rilievo in cui ogni pagina porta la serie grezza di misurazioni dietro al proprio grafico. Un lotto scansionato in cui ogni pagina conserva il risultato OCR che ha prodotto il suo layer di testo. Un corredo di tavole in cui ogni tavola porta l'estratto CAD da cui è stata renderizzata. In ognuno di questi casi una lista di allegati a livello di documento sarebbe una pila di file i cui nomi codificano i numeri di pagina: una convenzione, non una struttura
Un solo payload, due punti da cui viene referenziato
Il punto strutturale importante è che l'associazione a livello di pagina non crea una seconda copia di nulla. Il file viene incorporato una volta sola e registrato nel name tree EmbeddedFiles esattamente come un allegato a livello di documento, usando la stessa macchina della file specification. Ciò che cambia è dove finiscono il riferimento e la sua chiave di relazione: nel dizionario di pagina invece che nel catalogo del documento
Ne seguono due conseguenze. Primo: un reader che conosce solo gli allegati a livello di documento trova comunque il payload, perché sta nel name tree dove quel reader guarda. Secondo: cancellare l'associazione di pagina rimuove il binding, non il file. ClearPageAssociatedFiles stacca la pagina dai suoi file associati e lascia i payload raggiungibili attraverso il name tree, che è il comportamento conservativo: un'operazione che dice di cancellare l'associazione non deve distruggere in silenzio dati che un'altra parte del documento potrebbe referenziare
Quella funzione ha una condizione di successo volutamente ristretta che vale la pena conoscere. Riporta successo solo quando la pagina portava davvero una chiave /AF. Una pagina che non ha mai avuto associazioni restituisce fallimento invece di una conferma ottimista, così il chiamante non può scambiare un no-op per una pulizia completata
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Collega la serie di misurazioni che ha prodotto il grafico a pagina 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // file su disco
'measurements.csv', // nome visualizzato dentro il PDF
'text/csv', // tipo 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;
La stringa di relazione nella pratica non è testo libero. ISO 32000-2 definisce un vocabolario, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema e Unspecified, e i consumer si agganciano a quello. Data per i numeri dietro a un grafico, Source per il documento da cui una pagina è stata generata, Alternative per una rappresentazione equivalente. Scegli dal vocabolario anche quando nel tuo pipeline non c'è ancora nulla che lo legge, perché lo strumento successivo della catena potrebbe farlo
Perché la stessa lookup ha bisogno di FollowRef in entrambe le direzioni?
Perché seguire i riferimenti risponde a due domande diverse, e il codice deve sapere quale delle due sta facendo. Una lookup per chiave che segue i riferimenti indiretti restituisce l'oggetto a cui il riferimento punta. Una lookup che non li segue restituisce il riferimento stesso. Entrambe sono corrette, e usare quella sbagliata produce un malfunzionamento silenzioso invece di un errore
La lettura di un file associato mostra la prima direzione. Per ottenere il numero di oggetto dello stream incorporato dietro alle chiavi /EF e /F della file specification, la lookup non deve seguire il riferimento, perché seguendolo la reference si risolve nell'oggetto stream e il numero di oggetto è perduto. La regola si generalizza: qualsiasi percorso di codice che ha bisogno dell'identità di un oggetto anziché del suo contenuto deve prendere il riferimento grezzo
L'optional content mostra la direzione opposta, ed è costata di più da trovare. Il dizionario delle proprietà dell'optional content viene scritto nel catalogo come oggetto indiretto, quindi il codice che lo rilegge senza seguire i riferimenti ottiene una reference invece di un dizionario. Il type check su quel valore fallisce, e il ramo di fallback naturale — se non c'è configurazione, creane una — parte e sovrascrive la configurazione che c'era già. Nulla solleva eccezioni. I layer descritti in optional content groups e layer perdono semplicemente il proprio stato di visibilità predefinito
La lezione si generalizza oltre entrambi i casi. Quando una lookup può restituire o un riferimento o l'oggetto, un type check a se stante non è gestione degli errori: è un ramo che prima o poi verrà preso per la ragione sbagliata. Decidi esplicitamente ciò che ogni call site richiede, e preferisci l'API pubblica che risponde direttamente alla domanda, come una proprietà di conteggio dell'optional content, invece di infilarti in un accessor protected verso il dizionario del catalogo
// Gli allegati a livello di documento e le associazioni a livello di pagina coesistono.
// Un file incorporato può essere marcato come associato anche a livello di documento
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));
// La cancellazione stacca il binding di pagina; il payload resta nel name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Cosa fanno le modalità di conformità agli allegati
I profili di archiviazione limitano ciò che può essere incorporato, e la restrizione è applicata al punto di ingresso invece che al momento del salvataggio. PDF/A-1 vieta del tutto i file incorporati, PDF/A-2 ammette solo documenti PDF/A incorporati, e PDF/A-3 è il profilo che ha aperto l'incorporamento a tipi di file arbitrari, che è esattamente il motivo per cui i formati fattura ibridi si costruiscono su di esso
PDFlibPas rifiuta l'allegato quando la modalità di conformità attiva non lo consente, alla chiamata, non centinaia di operazioni dopo durante l'output. È una scelta deliberata su dove un errore costa meno da gestire: un rifiuto al call site nomina il file che stavi aggiungendo, mentre un rifiuto al salvataggio nomina un documento e ti lascia capire da solo quale dei quaranta allegati l'ha causato
È anche il motivo per cui i file associati compaiono così spesso nella fatturazione elettronica. Una fattura ibrida è un PDF che una persona legge con un payload XML leggibile dalla macchina allegato e marcato con la relazione giusta, e sia il profilo del contenitore sia la chiave di relazione fanno parte della specifica, non sono convenzioni. Quella costruzione è trattata in costruzione di fatture ibride Factur-X e ZUGFeRD, con il lato metadati in schema di estensione XMP di PDF/A-3
Quando l'associazione deve essere per pagina invece che per documento?
Quando un consumer ha bisogno di sapere a quale pagina appartengono i dati, e solo allora. Gli allegati a livello di documento sono più semplici, più ampiamente supportati dai viewer, e sufficienti ogni volta che il payload descrive l'intero documento: un XML di fattura, un manifest di firma, un archivio sorgenti. Ricorri all'associazione a livello di pagina quando il payload è genuinamente limitato alla pagina e l'identità della pagina fa parte del suo significato
Il supporto è il vincolo pratico. I file associati a livello di pagina sono un costrutto PDF 2.0, e il supporto dei viewer è più magro che per gli allegati a livello di documento. Dato che il payload sta comunque nel name tree, un viewer che ignora /AF sulle pagine mostra comunque il file nella sua lista allegati, quindi il degrado è graduale e senza rompere nulla. Ma se il binding di pagina è essenziale per il tuo consumer e non un metadato utile, verifica il reader a cui ti rivolgi davvero invece di dare per scontato
I file associati a livello di pagina, gli allegati a livello di documento e i gate dei profili di archiviazione che governano entrambi sono inclusi nella PDFlibPas Delphi PDF library. Se stai anche riparando file più vecchi in ingresso, il lavoro su metadati e conformità in conversione a PDF/A con riparazione dei metadati è ciò che decide, in primo luogo, quale di queste vie di allegato ti è disponibile