PDFlibPas koppelt een embedded file aan één specifieke pagina in plaats van aan het document als geheel, door een /AF-array in de page dictionary te schrijven terwijl de payload zelf gewoon geregistreerd blijft in de EmbeddedFiles name tree van het document. Die scheiding is precies wat ISO 32000-2 §14.13 beschrijft, en het is wat een reader de vraag laat beantwoorden die een documentniveau-bijlage niet kan: bij welke pagina horen deze data
De use cases zijn specifieker dan algemene bijlagen. Een onderzoeksrapport waarin elke pagina de ruwe meetreeks achter zijn grafiek meedraagt. Een gescande batch waarbij elke pagina het OCR-resultaat bewaart dat zijn tekstlaag heeft opgeleverd. Een tekeningenpakket waarin elk blad de CAD-extractie bijhoudt waaruit het is gerenderd. In elk geval zou een documentniveau-bijlagenlijst een stapel files opleveren met namen waarin paginanummers zijn verstopt, en dat is een conventie in plaats van een structuur
Eén payload, twee plaatsen waar ernaar verwezen wordt
Het belangrijke structurele punt is dat een associatie op paginaniveau nooit een tweede kopie van iets aanmaakt. De file wordt één keer geëmbed en geregistreerd in de EmbeddedFiles name tree, precies zoals een documentniveau-bijlage, met dezelfde file specification-machinery. Wat anders is, is waar de referentie en zijn relationship key worden weggeschreven: in de page dictionary in plaats van de document catalog
Twee gevolgen. Ten eerste vindt een reader die alleen documentniveau-bijlagen kent de payload alsnog, omdat die in de name tree staat waar zo'n reader kijkt. Ten tweede verwijdert het wissen van de pagina-associatie de binding, niet de file. ClearPageAssociatedFiles koppelt de pagina los van zijn associated files en laat de payloads bereikbaar via de name tree, en dat is het conservatieve gedrag: een operatie die zegt de associatie te wissen, mag niet stilletjes data vernietigen waar een ander deel van het document naar kan verwijzen
Die functie heeft één bewust nauwgezet succescriterium dat goed om weten te komen. Hij meldt alleen succes als de pagina daadwerkelijk een /AF-key droeg. Een pagina die nooit associaties had, geeft een failure terug in plaats van een opgewekte bevestiging, dus een aanroeper kan een no-op niet aanzien voor een afgeronde opruiming
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Koppel de meetreeks die de grafiek op pagina 3 heeft opgeleverd
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // bestand op schijf
'measurements.csv', // weergavenaam binnen de PDF
'text/csv', // MIME-type
'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;
De relationship string is in de praktijk geen vrije tekst. ISO 32000-2 definieert een vocabulaire, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema en Unspecified, en consumers sluiten daarop aan. Data voor de cijfers achter een grafiek, Source voor het document waaruit een pagina is gegenereerd, Alternative voor een equivalente weergave. Kies uit de vocabulaire, ook als er nog niets in uw pipeline de waarde leest, want de volgende tool in de keten kan dat wel
Waarom heeft dezelfde lookup FollowRef in beide richtingen nodig?
Omdat het volgen van referenties twee verschillende vragen beantwoordt, en de code moet weten welke van de twee hij stelt. Een key-lookup die indirecte referenties volgt, geeft het object terug waar de referentie naar wijst. Een lookup die niet volgt, geeft de referentie zelf terug. Beide zijn correct, en de verkeerde kiezen levert stilletjes wangedrag op in plaats van een fout
Het uitlezen van een associated file laat de eerste richting zien. Wilt u het objectnummer van de embedded stream achter de /EF- en /F-keys van de file specification hebben, dan mag de lookup juist niet volgen, want volgen resolved de referentie tot het stream-object en het objectnummer is dan weg. De regel veralgemeent: elke coderoute die een objectidentiteit nodig heeft in plaats van de objectinhoud, moet de rauwe referentie nemen
Optional content laat de tegenovergestelde richting zien, en die kostte meer zoekwerk. De optional content properties dictionary wordt als indirect object in de catalog geschreven, dus code die hem uitleest zonder te volgen, krijgt een referentie in plaats van een dictionary. Een typecheck op die waarde faalt dan, en de voor de hand liggende fallback-tak, maak er een als er geen configuratie is, draait en overschrijft de configuratie die er al stond. Nergens wordt een exceptie gegooid. De lagen uit optional content groups en lagen verliezen klakkeloos hun default visibility state
De les gaat verder dan beide gevallen. Als een lookup een referentie of het object kan teruggeven, is een blote typecheck geen fouthandling: het is een tak die uiteindelijk om de verkeerde reden wordt genomen. Beslis expliciet wat elke call site nodig heeft, en kies liever de publieke API die de vraag direct beantwoordt, zoals een optional-content count property, dan rechtstreeks in een protected accessor naar de catalog dictionary grijpen
// Documentniveau-bijlagen en paginaniveau-associaties bestaan naast elkaar.
// Een embedded file kan ook op documentniveau als associated gemarkeerd worden
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));
// Wissen koppelt de paginabinding los; de payload blijft in de name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Wat conformance modes met bijlagen doen
Archiveringsprofielen beperken wat er geëmbed mag worden, en die beperking wordt afgedwongen bij de entry point in plaats van bij het opslaan. PDF/A-1 verbiedt embedded files volledig, PDF/A-2 staat alleen embedded PDF/A-documenten toe, en PDF/A-3 is het profiel dat embedding opende voor willekeurige filetypes, en dat is precies waarom hybride factuurformaten erop zijn gebouwd
PDFlibPas weigert de bijlage zodra de actieve conformance mode het niet toelaat, ter plekke bij de aanroep, en niet honderden operaties later tijdens de output. Dat is een bewuste keuze over waar een fout het goedkoopst te verwerken is: een weigering op de call site noemt de file die u wilde toevoegen, terwijl een weigering bij het opslaan slechts een document noemt en u zelf laat uitzoeken welke van veertig bijlagen de boosdoener was
Dat is ook meteen de reden waarom associated files zo vaak opduiken in elektronische facturatie. Een hybride factuur is een PDF die een mens leest, met een machineleesbare XML-payload als bijlage en gemarkeerd met de juiste relationship, en zowel het containerprofiel als de relationship-key maken deel uit van de specificatie in plaats van een conventie. Die constructie staat in Factur-X en ZUGFeRD hybride facturen bouwen, met de metadatakant in het PDF/A-3 XMP extension schema
Wanneer hoort de associatie per pagina in plaats van per document?
Als een consumer moet weten bij welke pagina de data hoort, en alleen dan. Documentniveau-bijlagen zijn eenvoudiger, door meer viewers ondersteund, en volstaan zolang de payload het hele document beschrijft: een factuur-XML, een signature manifest, een source-archief. Grijp naar associatie op paginaniveau als de payload echt pagina-scoped is en de pagina-identiteit deel uitmaakt van de betekenis
Ondersteuning is de praktische beperking. Associated files op paginaniveau zijn een constructie uit PDF 2.0, en de viewerondersteuning is dunner dan die voor documentniveau-bijlagen. Omdat de payload hoe dan ook in de name tree zit, toont een viewer die /AF op pagina's negeert de file nog steeds in zijn bijlagenlijst, dus de degradatie is netjes. Maar als de paginabinding essentieel is voor uw consumer in plaats van handige metadata, verifieer dan de reader waar u echt op richt in plaats van het aan te nemen
Associated files op paginaniveau, documentniveau-bijlagen en de archiveringsprofiel-poorten die beide regelen, worden geleverd met de PDFlibPas Delphi PDF library. Als u onderweg ook oudere files repareert, bepaalt het metadata- en conformance-werk in converteren naar PDF/A met metadata-reparatie welke van deze bijlageroutes er überhaupt voor u openstaan