PDFlibPas přichytí vložený soubor k jedné konkrétní stránce místo k dokumentu jako celku tak, že zapíše pole /AF do slovníku stránky, zatímco samotný payload zůstává zaregistrovaný ve stromu názvů EmbeddedFiles dokumentu. Přesně tohle rozdělení popisuje ISO 32000-2 §14.13 a právě díky němu může čtečka zodpovědět otázku, na kterou příloha na úrovni dokumentu neumí odpovědět: ke které stránce ta data patří
Use cases jsou konkrétnější než u obecných příloh. Průzkumná zpráva, kde každá stránka nese surovou měřicí sérii za svým grafem. Naskenovaná dávka, kde každá stránka uchovává OCR výsledek, který vyrobil její textovou vrstvu. Sada výkresů, kde každý list nese CAD extrakt, z něhož byl vyrenderován. V každém takovém případě by seznam příloh na úrovni dokumentu byl hromada souborů s čísly stránek zakódovanými v názvech, což je konvence, ne struktura
Jeden payload, dvě místa, odkud se na něj odkazuje
Důležitý strukturální bod je, že asociace na úrovni stránky nevytváří druhou kopii ničeho. Soubor je vložen jednou a zaregistrovaný ve stromu názvů EmbeddedFiles úplně stejně jako příloha na úrovni dokumentu, se stejnou mechanikou file specification. Liší se jen to, kam se zapíše reference a její relační klíč: do slovníku stránky místo do katalogu dokumentu
Vyplývají z toho dvě věci. Za prvé, čtečka, která zná jen přílohy na úrovni dokumentu, payload i přesto najde, protože je ve stromu názvů, kam taková čtečka kouká. Za druhé, zrušení asociace stránky odstraní vazbu, nikoli soubor. ClearPageAssociatedFiles odpojí stránku od jejích associated files a nechá payloady dostupné přes strom názvů, což je konzervativní chování: operace, která říká zruš asociaci, nemá potichu ničit data, na která se může odkazovat jiná část dokumentu
Ta funkce má jednu záměrně úzkou podmínku úspěchu, o níž se vyplatí vědět. Úspěch hlásí jen tehdy, když stránka skutečně nesla klíč /AF. Stránka, která nikdy žádné asociace neměla, vrátí selhání místo veseleho potvrzení, takže volající si nemůže no-op splést s dokončeným úklidem
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Připoj měřicí sérii, která vyrobila graf na stránce 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // soubor na disku
'measurements.csv', // zobrazovaný název uvnitř PDF
'text/csv', // MIME typ
'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;
Relační řetězec není v praxi volný text. ISO 32000-2 definuje slovník Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema a Unspecified, a konzumenti se na něj napojují. Data pro čísla za grafem, Source pro dokument, z něhož stránka vznikla, Alternative pro ekvivalentní reprezentaci. Vybírejte ze slovníku, i když ho ve vaší pipeline zatím nikdo nečte, protože další nástroj v řadě číst může
Proč potřebuje tentýž lookup FollowRef na obě strany?
Protože následování referencí odpovídá na dvě různé otázky a kód musí vědět, kterou zrovna ptá. Lookup klíče, který následuje nepřímé reference, vrátí objekt, na nějž reference ukazuje. Lookup, který nenásleduje, vrátí samotnou referenci. Obojí je správně a použití toho špatného produkuje tiché špatné chování místo chyby
Čtení associated file ukazuje první směr. Chcete-li získat číslo objektu vloženého streamu za klíči /EF a /F file specification, nesmí lookup následovat, protože následování by referenci rozlišilo přímo na streamový objekt a číslo objektu by bylo pryč. Pravidlo se zobecňuje: každá kódová cesta, která potřebuje identitu objektu místo obsahu objektu, musí vzít surovou referenci
Optional content ukazuje opačný směr a tenhle stál hledání nejvíc. Slovník vlastností optional content se zapisuje do katalogu jako nepřímý objekt, takže kód, který ho čte zpět bez následování, dostane referenci místo slovníku. Typová kontrola nad tou hodnotou pak selže, spustí se přirozená záložní větev (není-li konfigurace, vytvoř ji) a přepíše konfiguraci, která už tam byla. Nic nevyhodí výjimku. Vrstvy popsané v článku optional content groups a vrstvy prostě ztratí svůj výchozí stav viditelnosti
Poučení se vztahuje i za oba tyhle případy. Když lookup může vrátit referenci i objekt, holá typová kontrola není error handling: je to větev, která se jednou zaveze ze špatného důvodu. Rozhodněte explicitně, co každé místo volání potřebuje, a upřednostněte veřejné API, které odpovídá na otázku přímo, třeba vlastnost s počtem optional content, před saháním do chráněného accessoru na katalogový slovník
// Přílohy na úrovni dokumentu a asociace na úrovni stránky koexistují.
// Vložený soubor může být označený jako associated i na úrovni dokumentu
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));
// Zrušení odpojí vazbu stránky; payload zůstává ve stromu názvů
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Jak konformační režimy nakládají s přílohami
Archivační profily omezují, co smí být vloženo, a omezení se vynucuje u vstupního bodu, ne až při ukládání. PDF/A-1 zakazuje vložené soubory úplně, PDF/A-2 povoluje jen vložené dokumenty PDF/A a PDF/A-3 je profil, který otevřel vkládání libovolným typům souborů, což je přesně důvod, proč na něm stojí hybridní formáty faktur
PDFlibPas odmítne přílohu, když ji aktivní konformační režim nedovoluje, a to hned u volání, ne stovky operací později při výstupu. To je záměrná volba místa, kde je chyba nejlevnější k vyřízení: odmítnutí u místa volání pojmenuje soubor, který jste přidávali, zatímco odmítnutí při ukládání pojmenuje jen dokument a nechá na vás, abyste vypátrali, která ze čtyřiceti příloh to způsobila
Proto se associated files tak často objevují v elektronickém fakturování. Hybridní faktura je PDF, které čte člověk, se strojově čitelným XML payloadem připojeným a označeným správným vztahem, a jak profil kontejneru, tak relační klíč jsou částí specifikace, nikoli konvencí. Tuhle konstrukci rozebírá článek stavění hybridních faktur Factur-X a ZUGFeRD, metadata stranu pokrývá XMP extension schema pro PDF/A-3
Kdy má být asociace na stránku místo na dokument?
Kdykoli konzument potřebuje vědět, ke které stránce data patří, a jen tehdy. Přílohy na úrovni dokumentu jsou jednodušší, šířeji podporované prohlížeči a stačí vždy, když payload popisuje celý dokument, fakturační XML, manifest podpisů, zdrojový archiv. Po asociaci na úrovni stránky sáhněte, když je payload skutečně vázaný na stránku a identita stránky je částí jeho významu
Praktickým limitem je podpora. Associated files na úrovni stránky jsou konstrukce PDF 2.0 a podpora v prohlížečích je tenčí než u příloh na úrovni dokumentu. Protože payload sedí ve stromu názvů tak jako tak, prohlížeč, který ignoruje /AF na stránkách, soubor v seznamu příloh stejně ukáže, takže degradace je graciézní. Když je ale vazba na stránku pro vašeho konzumenta nezbytná, ne jen užitečná metadata, ověřte si čtečku, kterou skutečně cílíte, místo domýšlení
Associated files na úrovni stránky, přílohy na úrovni dokumentu i archivační profily, které řídí obojí, přináší PDFlibPas Delphi PDF knihovna. Pokud cestou dovnitř opravujete i starší soubory, o tom, která z těchhle cest příloh vám vůbec bude k dispozici, rozhoduje práce s metadaty a konformací v článku konverze na PDF/A s opravou metadat