Teknisk artikel

Associated files på sideniveau i PDF 2.0 med PDFlibPas

PDFlibPas knytter en indlejret fil til én bestemt side i stedet for til dokumentet som helhed ved at skrive et /AF-array ind i page dictionary, mens selve payloaden forbliver registreret i dokumentets EmbeddedFiles name tree. Det er den opdeling, ISO 32000-2 §14.13 beskriver, og det er den, der gør det muligt for en reader at besvare det spørgsmål, et dokument-level-attachment ikke kan: hvilken side hører disse data til

Brugsscenarierne er mere specifikke end generelle attachments. En undersøgelsesrapport, hvor hver side bærer de rå måleserier bag sin graf. En scannet bunke, hvor hver side gemmer OCR-resultatet, der producerede sit tekstlag. Et tegningssæt, hvor hvert ark bærer CAD-ekstraktet, det blev renderet fra. I hvert tilfælde ville en dokument-level-attachmentliste være en bunke filer med navne, der koder sidenumre ind, hvilket er en konvention snarere end en struktur

Én payload, to steder den refereres fra

Det vigtige strukturelle punkt er, at en association på sideniveau ikke skaber en anden kopi af noget. Filen indlejres én gang og registreres i EmbeddedFiles name tree præcis som et dokument-level-attachment med det samme file specification-maskineri. Det, der adskiller sig, er, hvor referencen og dens relationship-nøgle skrives: ind i page dictionary i stedet for document catalog

To konsekvenser følger med. For det første finder en reader, der kun kender dokument-level-attachments, stadig payloaden, for den ligger i det name tree, sådan en reader kigger i. For det andet fjerner rydning af side-associationen bindingen, ikke filen. ClearPageAssociatedFiles kobler siden fra dens associated files og efterlader payloaderne tilgængelige gennem name tree, hvilket er den konservative adfærd: en operation, der siger ryd associationen, må ikke lydløst ødelægge data, som en anden del af dokumentet muligvis refererer til

Strukturen af en associated file på sideniveau i et PDF 2.0-dokument skrevet med PDFlibPas: payloaden indlejres én gang og registreres i EmbeddedFiles name tree under document catalog, mens page dictionary bærer et /AF-array, der refererer til samme file specification med en AFRelationship-nøgle, så ClearPageAssociatedFiles kobler bindingen fra uden at ødelægge data
Association på sideniveau tilføjer en anden reference, ikke en anden kopi: readers, der kun kender dokument-level-attachments, finder stadig payloaden i name tree, og rydning af side-bindingen efterlader den indlejrede stream tilgængelig

Den funktion har én bevidst snæver succesbetingelse, der er værd at kende. Den melder kun succes, når siden faktisk bar en /AF-nøgle. En side, der aldrig har haft associationer, returnerer fejl frem for en fornøjet bekræftelse, så en kalder kan ikke tage en no-op for en fuldført oprydning

var
  Lib: TPDFlib;
  Idx, I: Integer;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('survey-report.pdf');

    // Vedhæft måleserien, der producerede grafen på side 3
    Idx := Lib.AddPageAssociatedFileFromFile(3,
      'series-03.csv',            // fil på disk
      'measurements.csv',         // visningsnavn inde i PDF-filen
      '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;

Relationship-strengen er ikke fri tekst i praksis. ISO 32000-2 definerer et ordforråd, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema og Unspecified, og consumers læser efter det. Data for tallene bag en graf, Source for dokumentet, en side blev genereret fra, Alternative for en ækvivalent repræsentation. Vælg fra ordforrådet, selv når der endnu ikke er noget i din pipeline, der læser det, for det næste led i kæden gør det måske

Hvorfor skal det samme opslag bruge FollowRef i begge retninger?

Fordi referenceopfølgning besvarer to forskellige spørgsmål, og koden skal vide, hvilket den stiller. Et nøgleopslag, der følger indirekte referencer, returnerer objektet, referencen peger på. Et opslag, der ikke følger, returnerer referencen selv. Begge er korrekte, og at bruge det forkerte giver en lydløs fejladfærd frem for en fejl

At læse en associated file demonstrerer den første retning. For at få objektnummeret på den indlejrede stream bag file specificationens /EF- og /F-nøgler må opslaget ikke følge, for opfølgning resolver referencen til stream-objektet, og objektnummeret er væk. Reglen generaliserer: enhver kodevej, der har brug for et objekts identitet snarere end dets indhold, er nødt til at tage den rå reference

Optional content viser den modsatte retning, og den kostede mere at finde. Optional content properties-dictionary skrives ind i catalog som et indirekte objekt, så kode, der læser den tilbage uden at følge, får en reference frem for en dictionary. Et type-tjek på den værdi fejler så, og den naturlige fallback-gren, hvis der ingen konfiguration er, opret en, kører og overskriver den konfiguration, der allerede var der. Ingenting kaster en fejl. Lagene beskrevet i optional content groups og layers mister simpelthen deres default visibility state

Lektionen generaliserer ud over begge tilfælde. Når et opslag kan returnere enten en reference eller objektet, er et blottet type-tjek ikke fejlhåndtering: det er en gren, der før eller siden tages af den forkerte grund. Beslut eksplicit, hvad hvert call site har brug for, og foretræk den offentlige API, der besvarer spørgsmålet direkte, såsom en optional-content count-egenskab, frem for at grave ned i en protected accessor efter catalog-dictionary

Beslutningskort for referenceopfølgning i PDF-opslag som implementeret i PDFlibPas: læsning af /EF og /F under en file specification må ikke følge referencen, fordi den indlejrede streams objektnummer er svaret, mens den indirekte /OCProperties-dictionary i catalog skal følges, ellers overskriver et fejlet type-tjek lydløst den eksisterende optional content-konfiguration
Samme opslag besvarer to forskellige spørgsmål: identitet kræver den rå reference, indhold kræver det resolvte objekt, og et blottet type-tjek i stedet for den beslutning ender med at køre den forkerte gren uden at fejle højlydt
// Dokument-level-attachments og side-level-associationer lever side om side.
// En indlejret fil kan også markeres som associated på dokumentniveau
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));

// Rydning kobler side-bindingen fra; payloaden forbliver i name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
  Writeln('page 3 associations removed, payloads still reachable');

Hvad conformance modes gør ved attachments

Arkiveringsprofiler begrænser, hvad der må indlejres, og begrænsningen håndhæves ved indgangspunktet snarere end ved save-tid. PDF/A-1 forbyder indlejrede filer helt, PDF/A-2 tillader kun indlejrede PDF/A-dokumenter, og PDF/A-3 er profilen, der åbnede indlejring for vilkårlige filtyper, hvilket netop er grunden til, at hybride fakturaformater bygges på den

PDFlibPas nægter attachmentet, når den aktive conformance mode ikke tillader det, ved kaldet, ikke hundredvis af operationer senere under output. Det er et bevidst valg om, hvor en fejl er billigst at reagere på: en nægtelse på call site nævner filen, du var ved at tilføje, mens en nægtelse ved save-tid nævner et dokument og efterlader dig med at regne ud, hvilket af fyrre attachments der forårsagede den

Det er også derfor, associated files dukker op så ofte i elektronisk fakturering. En hybridfaktura er en PDF, et menneske læser, med en maskinlæsbar XML-payload vedhæftet og markeret med den rigtige relationship, og både container-profilen og relationship-nøglen er del af specifikationen snarere end konventioner. Den konstruktion er dækket i at bygge Factur-X- og ZUGFeRD-hybridfakturaer, med metadatasiden i PDF/A-3 XMP extension schema

Hvornår bør associationen være pr. side snarere end pr. dokument?

Når en consumer har brug for at vide, hvilken side dataene tilhører, og kun da. Dokument-level-attachments er enklere, bredere understøttet af viewers og tilstrækkelige, når payloaden beskriver hele dokumentet, en faktura-XML, en signature manifest, et source-arkiv. Ræk efter association på sideniveau, når payloaden reelt er side-scoped, og side-identiteten er en del af dens betydning

Understøttelse er den praktiske begrænsning. Associated files på sideniveau er en PDF 2.0-konstruktion, og viewer-understøttelsen er tyndere end for dokument-level-attachments. Fordi payloaden ligger i name tree uanset hvad, viser en viewer, der ignorerer /AF på sider, stadig filen i sin attachment-liste, så nedgraderingen er graceful. Men hvis side-bindingen er essentiel for din consumer snarere end nyttig metadata, verificér den reader, du reelt går efter, i stedet for at antage

Associated files på sideniveau, dokument-level-attachments og de arkiveringsprofil-porter, der styrer begge, følger med PDFlibPas Delphi PDF-biblioteket. Reparerer du også ældre filer på vejen ind, er metadata- og conformance-arbejdet i konvertering til PDF/A med metadata-reparation, det der afgør, hvilke af disse attachment-ruter der overhovedet står til din rådighed