V PDFium Component pred v3.121.1 je lahko branje pripombe prek TPdf.Annotation[] in dodelitev zapisa nazaj dodalo prazna vnosa /R in /D v njen slovar videzov /AP, tudi kadar je izvirnik nosil le /N. Validirniki PDF/A ta slovar zavrnejo. Od v3.121.1 pridobivalnik prijavi le videz, ki ga je dejansko prebral, tako da nespremenjen round-trip ne zapiše ničesar novega. Napako se splača razumeti podrobno, ker je običajni sprožilec popravek, ki naj bi datoteko naredil bolj skladno, ne manj
Kaj gre narobe, ko pripombo zapišete nazaj nespremenjeno?
Kratek odgovor: pripomba dobi tokove videzov, ki jih ni imela, in datoteka, ki je pred vašim urejanjem prestala validacijo PDF/A, jo po njem podere. Tipičen scenarij teče takole. Arhiv stranke pride s kvadratnimi in besedilnimi pripombami brez zastavice Print, PDF/A zahteva, da se vsaka pripomba natisne, zato se sprehodite po straneh, dodate afPrint in vsak zapis dodelite nazaj. Nič v tej kodi se ne dotakne videzov. Zapis iz TPdf.Annotation[] je TPdfAnnotation, SetAnnotationData pa zapiše vsako polje, katerega marker Has* je nastavljen, kar je točno tako, kot delujeta para HasContents / ContentsText. Težava je bila, da je pridobivalnik nastavil HasAppearanceRollover in HasAppearanceDown na True s praznimi nizi za načine, ki niso obstajali, nastavljavnik pa je dolžno zapisal dva prazna toka:
procedure MarkAnnotationsPrintable(const FileName: string);
var
Pdf: TPdf;
PageNo, I: Integer;
A: TPdfAnnotation;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
for PageNo := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := PageNo;
for I := 0 to Pdf.AnnotationCount - 1 do
begin
A := Pdf.Annotation[I];
if not (afPrint in A.Flags) then
begin
A.Flags := A.Flags + [afPrint] - [afHidden, afInvisible, afNoView];
// Pred v3.121.1 je ta dodelitev zapisala tudi prazna toka /AP/R in
// /AP/D, kadar je izvorna pripomba imela le /AP/N
Pdf.Annotation[I] := A;
end;
end;
end;
Pdf.SaveAs(ChangeFileExt(FileName, '.printable.pdf'));
finally
Pdf.Free;
end;
end;
ISO 32000-1 §12.5.5 definira slovar videzov s tremi vnosi: /N za normalni videz, /R za rollover in /D za down. /R in /D sta neobvezna, kadar ju ni, pa si gledalnik vrne /N. Prazen tok /R pa ni odsoten. Je veljaven tok, ki ne naslika ničesar, zato gledalnik, ki spoštuje videze rollover, pokaže prazen pravokotnik v trenutku, ko se kazalec premakne nad pripombo. PDF/A je še strožji: ISO 19005-1 (s Corrigendum 2) in ISO 19005-2 / 19005-3 dovoljujeta le /N v slovarju videzov pripombe. veraPDF prijavi round-trip datoteko pod pravilom 6.5.3-4 za PDF/A-1 in pravilom 6.3.3-2 za PDF/A-2 ter PDF/A-3, vgrajeni TPdf.ValidatePdfA pa jo našteje kot pvaiAnnotationApDictViolation. Ureditev, ki je dodala zastavico Print, da zadovolji eno odstavko standarda, pa je podrila drugo
Zakaj FPDFAnnot_GetAP vrne 2 za manjkajoči videz?
PDFium iz FPDFAnnot_GetAP nikoli ne vrne ničle, tudi kadar zahtevani tok videza ne obstaja. Funkcija sledi običajnemu dvoklicnemu vzorcu PDFium: podajte nil medpomnilnik, da dobite potrebno velikost v bajtih, rezervirajte, nato pa pokličite znova, da se prekopira besedilo UTF-16LE. Velikost vedno vključuje zaključevalnik UTF-16, zato manjkajoči tok prijavi 2 bajta, prazen niz skupaj z njegovim zaključevalnikom. Pridobivalnik pred v3.121.1 je preizkusil ByteLength >= SizeOf(FPDF_WCHAR), preizkus, ki ga prestane vsak klic, tako da so se vsi trije markerji HasAppearance* vrnili True za vsako pripombo s kakršnim koli videzom. Round-trip skozi zapis je nato od FPDFAnnot_SetAP zahteval, da shrani prazen niz za vsak način, PDFium pa je ustvaril tok, da ga nosi. Brez izjeme, brez opozorila, vidna stran pa je zgledala identična, zato se je napaka pokazala v fixture veraPDF, ne v gledalniku
Kako v3.121.1 odloči, da videz obstaja
ReadAppearance, pomočnik znotraj GetPageAnnotation, ki polni AppearanceNormal, AppearanceRollover in AppearanceDown, rezultat obravnava kot vsebino šele, kadar nosi vsaj en znak čez zaključevalnik. Prvi klic mora vrniti več kot SizeOf(FPDF_WCHAR) bajtov in sodo število bajtov, saj liha dolžina ne more biti UTF-16. Drugi klic, ki dejansko prekopira besedilo, se ponovno preveri: vrnjena dolžina 2 ali manj ali pa večja od rezerviranega medpomnilnika ponastavi HasValue na False in pusti niz prazen. Na strani pisanja se nič ni spremenilo. SetAnnotationData še vedno pokliče FPDFAnnot_SetAP le za načine, katerih zastavica HasAppearance* je True, tako da zapis, prebran iz pripombe, ki ima le /N, zdaj zapiše nazaj le /N. Regresijski fixture pokriva obe smeri: kvadratna pripomba z normalnim videzom, prebrana in nespremenjena zapisana nazaj, prestane PDF/A-1b, PDF/A-2b in PDF/A-3b, ista pripomba z odvzeto zastavico Print pa podre na pričakovanem pravilu zastavice in nič drugega
Manjkajoči in prazni tokovi zgledajo identično, zato pridobivalnik ostane previden
Izvorni API ne more ločiti manjkajočega toka videza od obstoječega, a praznega, PDFium Component se pa ne dela, da lahko. Oba primera vrneta iste 2 bajta iz FPDFAnnot_GetAP, tako da se oba prebereta kot HasAppearanceRollover = False s praznim AppearanceRollover. To ima dve posledici, okrog katerih naj bi oblikovali. Prvič, marker False pomeni »ni bilo prebrane vsebine, zato bo zapis nazaj ta način pustil pri miru«, ne »ključa /R ni v slovarju«. Drugič, zapis ne more zaznati praznega toka, ki je že v datoteki: dokument, poškodovan s starejšo gradnjo ali z drugim orodjem, se prebere kot čist, dodelitev zapisa nazaj ga ne popravi in ne poslabša. Da najdete take datoteke, potrebujete preizkus na ravni bajtov, zato sta tu TPdf.ValidatePdfA in delovni tok validacije PDF/A preflight s PDFium Component
Kako namerno počistite videz?
Marker nastavite izrecno in podate prazen niz; nastavljavnik ga zapiše. Zavračanje praznih nizov v SetAnnotationData bi bila za to napako kruta rešitev, podrla pa bi tudi klicatelje, ki videz namerno počistijo — isto pogodbo, ki ji za besedilo sledita HasContents in HasAuthor. Zato popravek živi v celoti v pridobivalniku, nastavljavnik pa še naprej ugodi kar koli zahteva klicatelj:
// Zamenjaj videz rollover, nato ga znova počisti
A := Pdf.Annotation[0];
A.HasAppearanceRollover := True;
A.AppearanceRollover := 'q Q';
Pdf.Annotation[0] := A;
A := Pdf.Annotation[0];
// A.HasAppearanceRollover je True in se besedilo round-tripa kot 'q Q'
A.HasAppearanceRollover := True; // izrecno ponovi namero
A.AppearanceRollover := ''; // namerno zapiši prazen tok
Pdf.Annotation[0] := A;
A := Pdf.Annotation[0];
// Prebere se kot HasAppearanceRollover = False s praznim nizom:
// prazen tok in manjkajoči sta tu nedločljiva
Upoštevajte, da izrecno izpraznjen /R ali /D še vedno šteje kot dodaten ključ pod pravili PDF/A, navedenimi zgoraj. Če je cilj arhivski profil, je pisanje nepraznega /N in puščanje drugih dveh načinov nedotaknjenih edina oblika, ki prestane validacijo. Vsak delovni tok, ki prestavlja pripombe med dokumenti, kot je izvoz in uvoz XFDF s PDFium Component, naj sledi istemu pravilu: prekopirajte načine, ki jih je vir dejansko imel, preostale markerje pa pustite False
Vzorec branje-spremeni-zapiši, ki ostane varen za PDF/A
Nadgradite na v3.121.1 ali pozneje, markerje videzov pustite točno take, kot jih je vrnil pridobivalnik, in shranjeno datoteko preverite, preden jo odpošljete. Ker se zastarel prazen tok prebere kot odsoten, mora korak preverjanja pogledati serializiran dokument, ne zapisa, in je dovolj poceni, da ga poženete po vsaki seriji:
uses
PDFium, FPdfPdfa; // FPdfPdfa deklarira TPdfAValidationIssue
function AnnotationAppearancesAreClean(Pdf: TPdf): Boolean;
var
Report: TPdfAValidationResult;
begin
// Preveri dokument, ki je trenutno naložen v Pdf, vključno z urejanji,
// narejenimi prek Pdf.Annotation[] od odpiranja
Report := Pdf.ValidatePdfA;
Result := not (pvaiAnnotationApDictViolation in Report.Issues);
end;
Ista disciplina velja za vsako podokno, ki strani pobarva ali označi za pregled, delovni tok, ki ga pokriva gradnja delovnega toka pregledovanja pripomb v Delphiju s PDFium Component: zapis je posnetek tistega, kar je pogon zmogel prebrati, marker, ki ga niste nastavili sami, pa naj potuje nazaj nespremenjen. Celoten API pripomb, PDF/A preflight in izvorni pogon PDFium prihajajo skupaj v PDFium Component za Delphi, C++Builder in Lazarus