Da iz Delphija pripnete izvorno datoteko na dokument PDF/A-3, zapiše PDFium Component verigo povezanih datotek PDF 2.0: vgrajeni tok datoteke z MIME /Subtype, specifikacijo datoteke, ki nosi /AFRelationship, in tabelo /AF, obešeno na katalog ali stran. InjectAssociateFiles in TPdf.SaveAsWithAssociateFiles zgradita to verigo v eni prirastni posodobitvi, od v3.121.2 pa se MIME tip serializira kot eno samo, pravilno ubežano ime PDF. Preostanek tega prispevka pokriva, kaj preverja validirnik, napako enega znaka, ki je podrila text/plain, in mesta, kjer so starejše izdaje tiho naredile kaj drugega, kot ste prosili
Kaj povezana datoteka PDF/A-3 dejansko potrebuje?
Priloga PDF/A-3 prestane validacijo šele, ko se trije objekti strinjajo med seboj: vgrajeni tok datoteke deklarira /Type /EmbeddedFile plus MIME /Subtype, slovar specifikacije datoteke (ISO 32000-2 §7.11.3) nosi /F, /UF, /EF in /AFRelationship, nekaj v dokumentu pa se na to specifikacijo datoteke sklicuje čez tabelo /AF (ISO 32000-2 §14.13). Golo vgrajevanje skozi drevo /Names /EmbeddedFiles, kar dela TPdf.CreateAttachment, polj združevanja sploh nikoli ne nastavi. Lasten validacijski fixture PDF/A-3b pri PDFium Component odvisnost napravi konkretno: preimenujte samo ključ /AFRelationship in datoteka podre točno eno pravilo v odstavku 6.8 ISO 19005-3; spustite samo MIME /Subtype in podre drugačno pravilo 6.8; isto prilogo dajte v kandidata PDF/A-1b in je zavrnjena na mestu, ker PDF/A-1 prepoveduje vgrajene datoteke, kar koli že urejene so metapodatki
Vrednost razmerja je del, ki ga ljudje radi uganejo. TPdfAFRelationship v FPdfAssocFiles preslika enega člana enuma na vsak žeton imena, ki ga injektor lahko izda, in le prvih pet pripada podmnožici, ki jo prepozna ISO 19005-3:
afSource→/Source: izvirnik, iz katerega je PDF izdelan, na primer datoteka za obdelavo besedil ali preglednicaafData→/Data: stroju berljivi podatki, iz katerih je vidna vsebina izpeljana ali ki jih predstavljaafAlternative→/Alternative,afSupplement→/Supplement,afUnspecified→/UnspecifiedafEncryptedPayload,afFormData,afTemplate: dodatki PDF 2.0, ki padajo izven podmnožice PDF/A-3, zato jih iz arhivskih izhodov držite zunaj
Zakaj je /Subtype /text/plain podrila validacijo?
Napaka MIME je bila napaka žetonizacije, ne vrzel skladnosti: pred v3.121.2 je injektor niz klicatelja prilepil naravnost za poševnico, iz česar je nastalo /Subtype /text/plain. V skladnji PDF druga poševnica zažene nov objekt imena (ISO 32000-1 §7.3.5), tako da je slovar toka nenadoma nosil ključ /Subtype, ime /text in obešeno dodatno ime /plain, ki je razvažalo parove ključ-vrednost. Neodvisen validirnik PDF/A je datoteko zavrnil med razčlenjevanjem slovarja EmbeddedFile, še preden je sploh dosegel pravilo PDF/A, zato je spodletelost zgledala kot pokvarjena datoteka, ne kot manjkajoča lastnost priloge
Popravek vrednost MIME pelje skozi EscapePdfName, ki izda /text#2Fplain: eno ime, katerega dekodirana vrednost je text/plain. Ubežanje je namerno širše od poševnice. Vsak bajt na 32 ali pod njim (presledek, tabulator, CR, LF), vsak bajt na 127 ali nad njim, ločila ()<>[]{}/% in sam ubežni znak # postanejo #XX. Ubežanje samo poševnice bi pustilo drugačno vrzel: niz MIME, ki vsebuje >> ali beli prostor, bi mogel zgodaj zapreti slovar ali vbrizgati dodatne ključe, zato regresijski preizkus požre sovražno vrednost z vsakim ločilom plus tabulator, LF in CR ter preveri točno kodiran izhod
// Kaj injektor zapiše za MIMEType = 'text/plain'
// pred v3.121.2: /Type /EmbeddedFile /Subtype /text/plain (dve imeni)
// v3.121.2: /Type /EmbeddedFile /Subtype /text#2Fplain (eno ime)
//
// Klicatelji vedno podajo običajno vrednost MIME. Če jo sami vnaprej ubežite,
// dvakrat kodirate '#', kar 'text#2Fplain' spremeni v 'text#232Fplain'
Options.Files[0].MIMEType := 'text/plain';
Gradnja datoteke PDF/A-3 z InjectAssociateFiles
Za izhod PDF/A-3 izdelajte skladni osnovni dokument s TPdf.SaveAsPdfAToStream in nato pokličite InjectAssociateFiles na tem toku; ta dvokorakni cevovod je točno tisto, kar validacijski fixture požene, preden prestane PDF/A-3b. TPdf.SaveAsWithAssociateFiles je priročni ovijalnik, shranjuje pa skozi običajno pot SaveAs s saRemoveSecurity, namesto skozi pisatelja PDF/A, zato ne doda identifikacije XMP in output intent, ki ju zahteva PDF/A. Upoštevajte, da živijo tipi zapisov v FPdfAssocFiles in FPdfPdfa, tako da pripadata obe enoti v vašemu stavku uses. Od v3.121.3 FileName in Description ne morata več biti le goli ASCII: /UF in /Desc se zapišeta kot niza besedil PDF, natisnljivi ASCII dobesedno in karkoli drugega kot UTF-16BE z oznako vrstnega reda bajtov, ime /F, starejše, pa je vedno prenosljiv natisnljivi ASCII z vsakim drugim znakom, zamenjanim s _, tako da bralci, ki /F dekodirajo s svojo kodno stranjo, pokažejo podčrtaj namesto mojibake. Starejše gradnje so vse tri pretvorile prek sistemske kodne strani ANSI na Delphiju ali zapisale surove bajte UTF-8 na Free Pascal, zato imena držite v ASCII le, če morajo starejše gradnje izdelati isti izhod
uses
System.SysUtils, System.Classes, System.IOUtils,
PDFium, FPdfPdfa, FPdfAssocFiles;
procedure SaveWithSourceData(Pdf: TPdf; const XmlPath, OutPath: string);
var
PdfAOptions: TPdfASaveOptions;
Options: TAssocFilesOptions;
Base: TMemoryStream;
Output: TFileStream;
begin
PdfAOptions := TPdfASaveOptions.Default;
PdfAOptions.Conformance := pac3b;
Options := TAssocFilesOptions.Default; // TargetPage = 0: /AF na ravni kataloga
SetLength(Options.Files, 1);
Options.Files[0].FileName := 'invoice-data.xml';
Options.Files[0].Description := 'Structured invoice data';
Options.Files[0].Content := TFile.ReadAllBytes(XmlPath);
Options.Files[0].Relationship := afData;
Options.Files[0].MIMEType := 'application/xml'; // zapisano kot /application#2Fxml
Base := TMemoryStream.Create;
try
if not Pdf.SaveAsPdfAToStream(Base, PdfAOptions) then
raise Exception.Create('PDF/A-3 base save failed');
Output := TFileStream.Create(OutPath, fmCreate);
try
InjectAssociateFiles(Base, Output, Options); // previje Base; ob spodletelosti sproži EPdfAssocFilesError
finally
Output.Free;
end;
finally
Base.Free;
end;
end;
Katalog ali stran: kam pristane tabela /AF?
TAssocFilesOptions.TargetPage odloči lastnika tabele /AF: 0 jo pripne katalogu kot združevanje na ravni dokumenta, 1..N pa slovarju te strani, z osnovo 1. Injektor doda vse kot eno prirastno posodobitev v fiksni razporeditvi (vgrajeni tokovi, nato specifikacije datotek, nato tabela /AF, nato prepisan katalog ali objekt strani), tako da obstoječi objekti obdržijo svoje odmike in se nič ne znova stisne. Vsak zgodnejši vnos /AF na ciljnem slovarju se zamenja, ne spoji, kar ponavljajoče shranjevanje naredi idempotentno, pomeni pa tudi, da zmaga drugi klic z drugačnim seznamom datotek. Dve obnašanji sta si nekdaj zaslužili varovalo v lastni kodi, oboje pa se je spremenilo. Pred v3.122.0 izven obsega TargetPage ni spodletel; vrnil se je na katalog, tako da je natipkanka pretvorila združevanje na ravni strani v združevanje na ravni dokumenta brez vsakega signala. Od v3.122.0 SaveAsWithAssociateFiles in SaveAsWithAssociateFilesToStream sprožita EPdfError, kadar je TargetPage izven 0..PageCount, InjectAssociateFiles pa sproži nov EPdfAssocFilesError za negativen TargetPage ali takšnega, ki ne poimenuje obstoječe strani, in pusti ciljni tok nespremenjen. Pred v3.121.4 je iskanje strani pregledalo shranjene bajte po slovarjih /Type /Page v vrstem redu datoteke, kar je lahko pripelo datoteko na drugačno stran, takrat ko so bili objekti strani shranjeni v drugačnem vrstem redu, kot so prikazani, na primer potem, ko so bile strani preurejene ali vstavljene; od v3.121.4 TargetPage poimenuje stran na tem položaju v vrstem redu strani dokumenta
procedure AttachChartSource(Pdf: TPdf; PageNumber: Integer;
const CsvBytes: TBytes; const OutPath: string);
var
Options: TAssocFilesOptions;
begin
// Od v3.122.0 TargetPage izven obsega sproži EPdfError (starejše gradnje
// so se tiho vrnila na /AF na ravni kataloga); preizkus najprej poimenuje stran
if (PageNumber < 1) or (PageNumber > Pdf.PageCount) then
raise EArgumentOutOfRangeException.CreateFmt('No page %d', [PageNumber]);
Options := TAssocFilesOptions.Default;
Options.TargetPage := PageNumber;
SetLength(Options.Files, 1);
Options.Files[0].FileName := 'chart-data.csv';
Options.Files[0].Content := CsvBytes;
Options.Files[0].Relationship := afSource;
Options.Files[0].MIMEType := 'text/csv';
if not Pdf.SaveAsWithAssociateFiles(OutPath, Options) then
raise Exception.Create('Associated-file save failed');
end;
Kako berete AFRelationship nazaj zanesljivo?
TPdf.AttachmentRelationship[Index] vrne ime /AFRelationship priloge skozi izvoz FPDFAttachment_GetAFRelationship, prazen niz pa ima dva možna pomena, zato najprej pokličite AttachmentRelationshipFeaturesAvailable. Vezava se nalaga strpno: kadar DLL PDFium tega izvoza nima, se vsako razmerje prebere kot prazno, kar je nedločljivo od specifikacije datoteke, ki preprosto nima /AFRelationship. Lastnost si deli svoj indeks s AttachmentCount, ki šteje vnose v drevesu /Names /EmbeddedFiles. Injektor zapiše le verigo /AF in ne doda vnosa v drevo imen, tako da je datoteka, pripeta prek InjectAssociateFiles, izven tega indeksa; da potrdite vbrizgano verigo, preglejte shranjene bajte ali poženite validirnik PDF/A. Notranjost tega drevesa imen pokriva delo s prilogami PDF v Delphiju s PDFium Component
procedure ReportRelationships(const FileName: string);
var
Pdf: TPdf;
I: Integer;
Rel: string;
begin
if not AttachmentRelationshipFeaturesAvailable then
begin
Writeln('This PDFium build cannot report /AFRelationship');
Exit; // prazen odgovor bi bil dvoumen, zato ne vprašajte
end;
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
for I := 0 to Pdf.AttachmentCount - 1 do
begin
Rel := Pdf.AttachmentRelationship[I];
if Rel = '' then
Rel := '(no /AFRelationship)';
Writeln(Pdf.AttachmentName[I], ': ', Rel);
end;
finally
Pdf.Free;
end;
end;
Kaj SaveAsWithAssociateFiles ne jamči?
TPdf.SaveAsWithAssociateFiles jamči ovojnico formata datoteke in to, da so bili zahtevani datoteki vbrizgani, ne skladnosti. Del vbrizga je nov: pred v3.122.0, kadar shranjeni bajti niso imeli berljivega repa ali se slovarja kataloga ni dalo najti, je InjectAssociateFiles vhod prekopiral nespremenjen skozi in metoda je še vedno vrnila True. Od v3.122.0 InjectAssociateFiles v teh primerih sproži EPdfAssocFilesError, preden kaj zapiše, SaveAsWithAssociateFiles vrne False, ker pa zdaj zgradi popoln izhod v shrambi shranjevanja, preden odpre cilj, spodletelo ali zavrnjeno shranjevanje obstoječe datoteke ni več odreže. Prazen seznam Files še vedno po načrtu prekopira dokument nespremenjen skozi. Vsebina koristnega tovora je prav tako vaša odgovornost: injektor ne preveri, da je datoteka XML dobro oblikovana, da se MIME tip ujema z bajti ali da je osnovni dokument sploh PDF/A. Končno datoteko obravnavajte kot nepreverjeno, dokler je ni videl validirnik — ista disciplina, opisana v PDFium Component in arhivski skladnosti PDF/A. Če tudi sami razčlenjujete dohodne slovarje, veljajo ista pravila imen #XX obrnjeno, tema, ki jo pokriva pasti žetonov imen pri razčlenjevanju slovarjev PDF
Povezane datoteke, izhod PDF/A, metapodatki prilog in validacija vsi odplujejo v isti komponenti, tako da cevovod zgoraj teče brez druge knjižnice PDF v gradnji. Sklic API, preizkusni prenos in licenčne možnosti so na strani izdelka PDFium Component