HotPDF v2.743.0 poravnava PDF bilješke koje nemaju izgledni tok /AP umjesto da ih tiho preskoči. FlattenLoadedAnnotations sada widget bez izgleda vodi kroz EnsureLoadedFieldAppearanceStream, a za oznake bez izgleda iz vlastitih svojstava bilješke gradi Form XObject, pa vrijednosti upisane u obrazac /NeedAppearances preživljavaju u sadržaju stranice umjesto da nestanu pri poravnavanju. Kvar koji je prisilio na ovu promjenu izgleda kao da se ništa nije dogodilo. Korisnik pošalje ispunjeni obrazac zahtjeva, ispisan u PDF iz preglednika. Učitate ga u HotPDF, pozovete FlattenLoadedAnnotations, dobijete 0, spremite i isporučite dokument s praznim okvirima ondje gdje je podnositelj upisao ime i iznos. Ništa nije iznimka podiglo ni zapisalo. Vrijednosti su cijelo vrijeme bile u datoteci, u unosu /V svakog polja, a prolaz poravnavanja prošao je ravno pokraj njih jer nijedan od tih widgeta nije nosio izgledni tok koji bi trebalo ugraditi
Zašto obrazac ispisan iz preglednika gubi upisane vrijednosti pri poravnavanju?
Jer obrazac /NeedAppearances sprema vrijednost, ali ne sprema sliku vrijednosti. ISO 32000-1 12.7.2 dopušta interaktivnom obrascu da u rječniku AcroForm postavi /NeedAppearances true, što pregledniku govori da pri otvaranju izgradi vizualnu površinu svakog polja iz /V, /DA i /Q. Proizvođači koji jeftino stvaraju obrasce — putovi ispisa iz preglednika, punjači na poslužitelju, neki ulazi za skeniranje — prihvaćaju tu mogućnost i uopće ne zapisuju /AP. Poravnavanje, kako ga definira algoritam izgleda u ISO 32000-1 12.5.5, posao je prepisivanja: uzeti normalni izgledni tok bilješke, preslikati njegov /BBox na /Rect, pozvati ga iz toka sadržaja stranice operatorom Do i zatim izbrisati bilješku. Bez izvornog toka nema se što prepisati. Izvorna implementacija HotPDF-a iz v2.386.0 to je tretirala kao preskakanje, što je pojedinačno obranjivo, a u cjelini pogubno: dokumenti kojima je poravnavanje najpotrebnije najmanje će vjerojatno nositi izglede. Ista je praznina progutala oznake — Highlight iz alata za reviziju, Square iz redakcijskog prolaza, Ink potpis — svaki put kada se proizvođač oslonio na preglednik da ih nacrta
Gdje HotPDF umeće stvaranje u FlattenLoadedAnnotations
Točka umetanja namjerno je kasna: nakon neuspješne pretrage izgleda, a ne prije nje. FlattenLoadedAnnotations i dalje najprije traži normalni izgled kroz GetLoadedAnnotationAppearanceStream, a bilješka koja ga već ima ugrađuje se točno kao u v2.386.0. Samo rezultat nil, na bilješci s nedegeneriranim /Rect i bez skrivene zastavice, ulazi u put stvaranja. Taj redoslijed je važan: autor dokumenta koji se potrudio zapisati /AP dobiva natrag vlastite bajtove, a ne HotPDF-ovu rekonstrukciju
NStrm:= GetLoadedAnnotationAppearanceStream(Indices[PgI], AnI, aakNormal);
if (NStrm= nil) and (RR> RL) and (RT> RB) and ((FlagsValue and 2)= 0) then
begin
if Subtype= 'Widget' then
begin
FieldIdx:= GetLoadedFormFieldIndexForAnnotation(Indices[PgI], AnI, WidgetIdx);
if FieldIdx>= 0 then
EnsureLoadedFieldAppearanceStream(FieldIdx);
// pokušaj ponovno: generator je widgetu dodao /AP /N
NStrm:= GetLoadedAnnotationAppearanceStream(Indices[PgI], AnI, aakNormal);
end
else
NStrm:= SynthesizeMarkupAppearance(AnnotDict, Subtype, RL, RB, RR, RT);
end;
Od tog se mjesta dvije obitelji bilješki razdvajaju. Widget se preko GetLoadedFormFieldIndexForAnnotation razriješi natrag do polja kojem pripada i preda funkciji EnsureLoadedFieldAppearanceStream, generatoru izgleda polja koji je u ovoj Delphi PDF biblioteci od v2.328.0. Ponovna upotreba njega umjesto pisanja drugog renderera polja cijela je poanta — već pokriva Type0 fontove, prijelom redaka, quadding, stanja /AS potvrdnih okvira i radio-gumbi te rotaciju /MK, istu mehaniku iza dodavanja AcroForm polja u već učitani PDF. Sve ostalo ide sintetizatoru oznaka. Pozivatelju se ništa ne mijenja: isti jednoredni poziv za poravnavanje sada vraća različit od nule na dokumentima koji su prije vraćali nulu
Doc:= THotPDF.Create(nil);
try
Doc.LoadFromFile('needappearances-form.pdf');
// v2.743.0: widgeti i oznake bez AP-a se stvaraju, zatim ugrađuju
Flattened:= Doc.FlattenLoadedAnnotations; // sve stranice, svi podtipovi
// Flattened:= Doc.FlattenLoadedAnnotations('1-3', 'Highlight');
if Flattened= 0 then
raise Exception.Create('nothing was flattened');
Doc.SaveLoadedDocument('flattened.pdf');
finally
Doc.Free;
end;
Zašto QuadPoints i InkList završavaju na pogrešnom mjestu?
Jer su te koordinate u korisničkom prostoru stranice, dok sintetizirani izgled crta u vlastitom prostoru /BBox, a ishodišta nisu ista točka. Tablica 176 standarda ISO 32000-1 definira /QuadPoints za bilješke tekstualnih oznaka u zadanom korisničkom prostoru, a Tablica 174 isto čini za krajnje točke /L bilješke crte; /InkList slijedi istu konvenciju. HotPDF sintetiziranom obrascu daje /BBox oblika [0 0 W H], čije je ishodište u donjem lijevom kutu /Rect. Zato se svaka točka izvučena iz /QuadPoints, /L ili /InkList prije upisa u tok sadržaja mora prevesti negacijom donjeg lijevog kuta /Rect. Pogriješite li i istaknuće na retku 700 točaka više na stranici nacrtat ćete 700 točaka iznad vlastitog okvira, što u praksi znači da se ne crta nigdje. Ispravak je jedno oduzimanje po koordinati i slaže se s cm koji bake emitira poslije — ta matrica vraća /BBox na /Rect, pa se ta dva koraka poništavaju u ispravnu apsolutnu geometriju
// Krajnje točke /L su u korisničkom prostoru stranice (ISO 32000-1 Tablica 174); ishodište
// BBox-a obrasca nalazi se u donjem lijevom kutu /Rect, pa ga pomakni za -(RL, RB)
X1:= ArrNum(LA, 0, 0)- RL;
Y1:= ArrNum(LA, 1, 0)- RB;
X2:= ArrNum(LA, 2, 0)- RL;
Y2:= ArrNum(LA, 3, 0)- RB;
StrokeOp:= ColorOp(DArr('C'), true);
if StrokeOp= '' then
StrokeOp:= '0 G';
Result:= _FloatToStrR(BW)+ ' w '#10+ StrokeOp+ #10+
_FloatToStrR(X1)+ ' '+ _FloatToStrR(Y1)+ ' m '+
_FloatToStrR(X2)+ ' '+ _FloatToStrR(Y2)+ ' l S'#10;
Što sintetizirani izgled oznake zapravo crta?
Sintetizator oznaka čita rječnik bilješke i ništa drugo, čime izlaz ostaje predvidljiv i pošten prema onome što ne može znati. FreeText i Stamp crtaju /Contents fontom i bojom izvučenima iz /DA, poravnato prema /Q, s unutarnjim rubom od 2 pt. Square i Circle crtaju obris re ili Bezierov obris s četiri luka, iscrtan u /C, a ispunjen s /IC kada je prisutan, širinom iz /BS /W. Line i Ink iscrtavaju svoje vrhove. Highlight ispunjava svaki četverokut, dok Underline, StrikeOut i Squiggly iscrtavaju crtu na dnu četverokuta, na njegovoj sredini ili kao cik-cak od jedne točke. /CA manji od 1 postaje ExtGState s unosom ca, referenciran kao /GSA gs na početku toka
Kodiranje teksta odlučuje se iz unosa /DR /Font u AcroFormu koji imenuje /DA. Ako je /Subtype tog fonta Type0, HotPDF zapisuje string kao heksadecimalni literal UTF-16BE s oznakom redoslijeda bajtova FEFF; inače zapisuje string s literalnim navodnicima, uz escape za zagrade i kose crte te oktalni zapis bajtova iznad 126. Operator Tf iz /DA emitira se prije BT, što je dopušteno jer stanje teksta preživljava granicu tekstualnog objekta, a štedi rastavljanje niza /DA. Dva ograničenja vrijedi navesti jasno. Širina retka za prijelom i quadding procjenjuje se heuristikom pola em-a / cijelog em-a umjesto stvarnim metrikama fonta, pa je poravnanje na proporcionalnom fontu blizu, ali nije točno. A podtip bez ičega što se može sintetizirati — Popup, Link, Stamp čiji je jedini sadržaj ime ikone — daje nil i ostavlja se netaknutim, baš kao i prije
Privremena zamjena /Annots koja kažnjava korisno čišćenje
FlattenOneWidget, put po widgetu koji koristi FlattenLoadedFormFields, zamka je s aliasima koju mora poštovati svaka promjena unutar zajedničke petlje poravnavanja. Privremeno zamjenjuje vrijednost stranice /Annots poljem od jednog elementa kako bi se generički prolaz poravnavanja izvršio nad jednim widgetom, a zatim u bloku finally vraća izvorni pokazivač PHPDFDictionaryItem. Vraćanje zapisuje natrag u utor rječnika koji je uhvatio prije poziva
DictItem:= PHPDFDictionaryItem(PageObj.Items.Items[AnnotsIndex]);
Item:= DictItem^.Value;
TemporaryAnnots:= THPDFArrayObject.Create(nil);
TemporaryAnnots.AddObject(Target);
DictItem^.Value:= TemporaryAnnots;
try
Result:= FlattenLoadedAnnotations(IntToStr(PageIndex+ 1), 'Widget')= 1;
finally
DictItem^.Value:= Item; // viseći pokazivač ako je unutarnja petlja oslobodila ovu stavku
TemporaryAnnots.Free;
end;
Dodajte razumno pospremanje unutar zajedničke unutarnje petlje — DeleteValue('Annots') nakon pražnjenja polja, kako spremljena stranica ne bi nosila prazno zaostalo polje — i taj će poziv osloboditi upravo stavku rječnika na koju pokazuje DictItem. finally tada zapisuje kroz viseći pokazivač i proces umire s porukom Invalid pointer operation. Dva postojeća testa odmah su ga uhvatila, što je jedini razlog zašto je ovo fusnota, a ne prijava podršci. Pravilo je općenito: prije dodavanja čišćenja u zajedničku petlju provjerite ugovore pozivatelja o aliasima ili zamjenama. Preostalo prazno polje /Annots kozmetička je nepravilnost i ne vrijedi za nju mijenjati jamstvo životnog vijeka pokazivača
Što ostaje neugrađeno i koliko poravnavanje košta
Skrivene bilješke namjerno su isključene. Bilješka čiji cijeli broj /F ima postavljen bit na položaju 2 skrivena je prema ISO 32000-1 12.5.3, a kada nema ni /AP, stvarno je primamljivo sintetizirati joj izgled i ugraditi ga kao i sve ostalo. To bi bila pogreška sa sigurnosnim posljedicama: ugradnja nevidljive bilješke u sadržaj stranice učinila bi je vidljivom svima koji otvore datoteku. HotPDF te bilješke ostavlja točno ondje gdje jesu i ne broji ih u povratnoj vrijednosti. Jednako jasno objasnite korisnicima cijenu onih koje se doista ugrade. Poravnavanje je nepovratno — bilješka se briše iz polja /Annots stranice, a njezin vizual sada je sadržaj stranice, pa više nema uređivanja vrijednosti polja, niti teme komentara, niti promjene stanja /AS, niti načina za oporavak strukturiranih podataka osim izvorne datoteke. Poravnajte kopiju, čuvajte izvornik i posegnite za njim samo kada dokument prestane biti obrazac i postane zapis. Ako je vaš problem zasnovan na XFA-u, a ne na izgledu koji nedostaje, zaseban put poravnavanja XFA u AcroForm u HotPDF-u mjesto je od kojeg treba početi, a ako obrazac još gradite, bilješke o povezivanju akcija i validacije AcroForm polja pokrivaju stranu za pisanje
Jedna napomena o provjeri, jer bi vam inače odnijela poslijepodne. ExtractLoadedPageGlyphs ne spušta se u Form XObjecte, a ugrađeni izgled živi unutar jednoga — tok sadržaja stranice drži samo slijed q ... cm /FlatAn<n> Do Q. Izdvajanje glifova na poravnatoj stranici zato ne prijavljuje ništa i to je ispravno ponašanje, a ne izgubljeno ugrađivanje. Provjerite ili na razini bajtova, tražeći naziv resursa /FlatAn, poziv Do i /Subtype /Form, ili kroz cjevovod iscrtavanja koji XObjecte doista proširuje
Poravnavanje bilješki izgleda kao tri retka prepisivanja sve dok ne sretnete dokumente koje ljudi stvarno stvaraju. Ako u Delphiju ili C++Builderu radite s ispunjenim obrascima, oznakama revizije ili arhivskim izlazom, vrijedi pročitati kako HotPDF Delphi PDF component obrađuje stranu učitanih dokumenata AcroForm obrazaca i bilješki prije nego što na njemu izgradite vlastiti generator izgleda