Techninis straipsnis

PDF anotacijų be /AP srauto išlyginimas Delphi

HotPDF v2.743.0 išlygina PDF anotacijas, neturinčias /AP išvaizdos srauto, užuot tyliai jas praleidusi. FlattenLoadedAnnotations dabar anotacijų neturintį widget nukreipia per EnsureLoadedFieldAppearanceStream, o anotacijų be išvaizdos žymėjimams iš pačių anotacijos savybių sukuria Form XObject, todėl į /NeedAppearances formą įvestos reikšmės išlieka puslapio turinyje, o ne dingsta išlyginimo metu. Pakeitimą privertęs gedimas atrodo kaip nieko nedarantis veiksmas. Klientas atsiunčia užpildytą paraiškos formą, naršykle išspausdintą į PDF. Įkeliate ją į HotPDF, iškviečiate FlattenLoadedAnnotations, gaunate 0, išsaugote ir išsiunčiate dokumentą su tuščiais laukeliais ten, kur pareiškėjas įrašė vardą ir sumą. Nieko nebuvo iškelta ir nieko neužregistruota. Reikšmės visą laiką buvo faile, kiekvieno lauko /V įraše, o išlyginimo etapas tiesiog praėjo pro šalį, nes nė vienas widget neturėjo išvaizdos srauto, kurį būtų galima įrašyti

Kodėl naršyklėje išspausdintos formos išlyginimas praranda įvestas reikšmes?

Nes /NeedAppearances formoje saugoma reikšmė, bet ne jos vaizdas. ISO 32000-1 12.7.2 leidžia interaktyviai formai AcroForm žodyne nustatyti /NeedAppearances true, o tai nurodo peržiūros programai atvėrimo metu iš /V, /DA ir /Q sukurti kiekvieno lauko vaizdinį paviršių. Pigiai formas kuriantys gamintojai – naršyklės spausdinimo keliai, serverio užpildytojai ir kai kurie skenavimo įrankiai – šiuo pasiūlymu pasinaudoja ir visai nerašo /AP. Išlyginimas, apibrėžtas ISO 32000-1 12.5.5 išvaizdos algoritme, yra perrašymo užduotis: paimti įprastą anotacijos išvaizdos srautą, susieti jo /BBox su /Rect, iškviesti jį puslapio turinio sraute operatoriumi Do, tada ištrinti anotaciją. Neturint šaltinio srauto nėra ko perrašyti. Pradinė HotPDF realizacija nuo v2.386.0 tai traktavo kaip „skip“, o atskirai tai ginama, bet bendrai yra pražūtinga: dokumentai, kuriems išlyginimo labiausiai reikia, mažiausiai tikėtina, kad turi išvaizdas. Ta pati spraga prarydavo ir žymėjimus – Highlight iš peržiūros įrankio, Square iš redline etapo ar Ink parašą – kai gamintojas tikėjosi, kad juos nupieš peržiūros programa

Kur HotPDF prijungia sintezę prie FlattenLoadedAnnotations?

Prijungimo taškas sąmoningai vėlyvas: po to, kai išvaizdos paieška nepavyksta, o ne prieš ją. FlattenLoadedAnnotations vis dar pirmiausia prašo įprastos išvaizdos per GetLoadedAnnotationAppearanceStream, o anotacija, kuri ją jau turi, įrašoma lygiai taip, kaip v2.386.0. Tik nil rezultatas anotacijai, turinčiai nedegeneruotą /Rect ir neturinčiai hidden vėliavos, patenka į sintezės kelią. Ši tvarka svarbi: dokumento autorius, įdėjęs /AP, atgauna savo baitus, o ne HotPDF jų rekonstrukciją

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);
    // klausiame dar kartą: generatorius prie widget prijungė /AP /N
    NStrm:= GetLoadedAnnotationAppearanceStream(Indices[PgI], AnI, aakNormal);
  end
  else
    NStrm:= SynthesizeMarkupAppearance(AnnotDict, Subtype, RL, RB, RR, RT);
end;

Nuo šio taško dvi anotacijų šeimos išsiskiria. Widget per GetLoadedFormFieldIndexForAnnotation susiejamas su laukui priklausančiu indeksu ir perduodamas EnsureLoadedFieldAppearanceStream – lauko išvaizdos generatoriui, kuris šioje Delphi PDF bibliotekoje yra nuo v2.328.0. Pakartotinai jį naudoti, o ne rašyti antrą lauko atvaizduotuvą, ir yra visa esmė: jis jau apima Type0 šriftus, eilučių laužymą, quadding, checkbox ir radio /AS būsenas bei /MK pasukimą, tą pačią mechaniką, kuri naudojama pridedant AcroForm laukus jau įkeltame PDF. Visa kita keliauja į žymėjimo sintezatorių. Iškvietėjui niekas nesikeičia: tas pats vienos eilutės išlyginimo iškvietimas dabar grąžina nenulinį skaičių dokumentams, kurie anksčiau grąžindavo nulį

Doc:= THotPDF.Create(nil);
try
  Doc.LoadFromFile('needappearances-form.pdf');
  // v2.743.0: widget ir žymėjimų išvaizdos be AP sugeneruojamos ir įrašomos
  Flattened:= Doc.FlattenLoadedAnnotations;          // visi puslapiai, visi tipai
  // Flattened:= Doc.FlattenLoadedAnnotations('1-3', 'Highlight');
  if Flattened= 0 then
    raise Exception.Create('nothing was flattened');
  Doc.SaveLoadedDocument('flattened.pdf');
finally
  Doc.Free;
end;

Kodėl QuadPoints ir InkList atsiduria netinkamoje vietoje?

Nes šios koordinatės yra puslapio naudotojo erdvėje, o sintetinis išvaizdos srautas piešiamas savo /BBox erdvėje, ir šie du pradžios taškai nėra tas pats. ISO 32000-1 176 lentelė apibrėžia teksto žymėjimo anotacijų /QuadPoints numatytoje naudotojo erdvėje, o 174 lentelė tą patį daro linijos anotacijos /L galiniams taškams; /InkList laikosi tos pačios konvencijos. HotPDF sintetiniam Form suteikia /BBox [0 0 W H], kurio pradžia yra /Rect apatiniajame kairiajame kampe. Todėl kiekvieną tašką, paimtą iš /QuadPoints, /L ar /InkList, prieš įrašant į turinio srautą reikia pastumti neigta /Rect apatinio kairiojo kampo reikšme. Suklyskite ir paryškinimas eilutėje, esančioje 700 taškų aukščiau puslapyje, bus nupieštas 700 taškų virš savo laukelio, o praktiškai tai reiškia, kad jis niekur nepasirodys. Pataisymas yra vienas atėmimas kiekvienai koordinatei, jis dera su vėliau išlyginimo išvedama cm matrica – ši matrica perkelia /BBox atgal ant /Rect, todėl abu veiksmai susidėlioja į teisingą absoliučią geometriją

// /L galiniai taškai yra puslapio naudotojo erdvėje (ISO 32000-1 174 lentelė); Form
// BBox pradžia yra /Rect apatiniame kairiajame kampe, todėl pastumiame per -(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;

Ką iš tikrųjų piešia sintetinis žymėjimo vaizdas?

Žymėjimų sintezatorius skaito tik anotacijos žodyną, todėl išvestis lieka nuspėjama ir sąžiningai parodo, ko jis negali žinoti. FreeText ir Stamp piešia /Contents naudodami iš /DA išanalizuotą šriftą ir spalvą, sulygiuoja pagal /Q ir palieka 2 pt paraštes. Square ir Circle piešia re arba keturių lankų Bezier kontūrą, brėžiamą su /C ir užpildomą /IC, jei jis pateiktas, o plotį ima iš /BS /W. Line ir Ink brėžia savo viršūnes. Highlight užpildo kiekvieną keturkampį, o Underline, StrikeOut ir Squiggly brėžia liniją keturkampio apačioje, per jo vidurį arba kaip vieno taško zigzagą. /CA, mažesnis už 1, tampa ExtGState su ca įrašu, nurodomu kaip /GSA gs srauto pradžioje

Teksto koduotė nustatoma iš AcroForm /DR /Font įrašo, kurį nurodo /DA. Jei to šrifto /Subtype yra Type0, HotPDF eilutę rašo kaip UTF-16BE šešioliktainį literalą su FEFF baitų tvarkos žyme; kitu atveju rašoma escape pažymėta literalų eilutė, kurioje skliaustai ir pasvirieji brūkšniai apsaugomi, o baitai virš 126 rašomi aštuntainiu formatu. /DA operatorius Tf išvedamas prieš BT, o tai teisėta, nes teksto būsena išlieka tarp teksto objekto ribų, ir taip nereikia išskaidyti /DA eilutės. Verta aiškiai įvardyti du apribojimus. Eilutės plotis laužymui ir quadding apskaičiuojamas puse em / visu em paremta euristika, o ne tikromis šrifto metrikomis, todėl lygiavimas proporciniame šrifte yra artimas, bet ne tikslus. O tipas, kuriam nėra ko sintetinti – Popup, Link arba Stamp, kurio vienintelis turinys yra piktogramos vardas – grąžina nil ir lieka nepakeistas, kaip ir anksčiau

Laikinas /Annots pakeitimas, baudžiantis už pernelyg uolų tvarkymą

FlattenOneWidget, vieno widget kelias, kurį naudoja FlattenLoadedFormFields, yra aliasing spąstai, kurių turi paisyti bet koks pakeitimas bendrame išlyginimo cikle. Jis laikinai pakeičia puslapio /Annots reikšmę vieno elemento masyvu, kad bendras išlyginimo etapas veiktų su vienu widget, o tada finally bloke grąžina pradinę PHPDFDictionaryItem rodyklę. Atkūrimas rašo atgal į žodyno vietą, kurią užfiksavo prieš iškvietimą

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;   // kabanti, jei vidinis ciklas atlaisvino šį elementą
  TemporaryAnnots.Free;
end;

Į bendro vidinio ciklo vidų pridėkite racionaliai atrodantį sutvarkymą – DeleteValue('Annots'), kai masyvas ištuštėja, kad išsaugotame puslapyje neliktų tuščio likučio – ir tas iškvietimas atlaisvins būtent tą žodyno elementą, į kurį rodo DictItem. Tada finally rašys per kabančią rodyklę, o procesas žus su „Invalid pointer operation“. Du esami testai tai aptiko iškart, todėl ši detalė liko išnaša, o ne palaikymo bilietas. Taisyklė bendra: prieš pridėdami tvarkymą į bendrą ciklą patikrinkite iškvietėjus dėl alias arba swap sutarčių. Likęs tuščias /Annots masyvas yra kosmetinis trūkumas ir neverta dėl jo keisti rodyklės gyvavimo trukmės garantijos

Kas lieka neįrašyta ir kiek kainuoja išlyginimas

Paslėptos anotacijos sąmoningai neįtraukiamos. Anotacija, kurios /F sveikasis skaičius turi nustatytą 2 bitą, pagal ISO 32000-1 12.5.3 yra paslėpta, o kai ji dar neturi /AP, kyla tikra pagunda ją sugeneruoti ir įrašyti kaip visas kitas. Tai būtų saugumo pasekmių turinti klaida: nematomą pastabą įrašius į puslapio turinį, ji tampa matoma kiekvienam, kuris atveria failą. HotPDF tokias anotacijas palieka vietoje ir neįskaičiuoja į grąžinamą reikšmę. Lygiai taip pat aiškiai paaiškinkite naudotojams tų anotacijų, kurios įrašomos, kainą. Išlyginimas negrįžtamas – anotacija pašalinama iš puslapio /Annots masyvo, o jos vaizdas tampa puslapio turiniu, todėl nebegalima redaguoti lauko reikšmės, komentarų gijos, perjungti /AS būsenos ir atkurti struktūrinių duomenų be pradinio failo. Išlyginkite kopiją, pasilikite originalą ir naudokite šį kelią tik tada, kai dokumentas nustoja būti forma ir tampa įrašu. Jei problema susijusi su XFA, o ne su išvaizdos nebuvimu, pradėkite nuo atskiro XFA į AcroForm išlyginimo kelio HotPDF, o jei formą dar kuriate, rašymo pusei skirtos pastabos apie AcroForm laukų veiksmų ir tikrinimo sujungimą

Dar viena tikrinimo pastaba, nes kitaip ji kainuos popietę. ExtractLoadedPageGlyphs neina į Form XObject vidų, o įrašyta išvaizda gyvena būtent ten – puslapio turinio sraute lieka tik q ... cm /FlatAn<n> Do Q seka. Todėl glifų išgavimas iš išlyginto puslapio nieko neranda, ir tai yra teisingas elgesys, o ne prarastas įrašymas. Tikrinkite baitų lygiu, ieškodami /FlatAn resurso vardo, Do iškvietimo ir /Subtype /Form, arba per atvaizdavimo konvejerį, kuris XObject išplečia

Anotacijų išlyginimas atrodo kaip trys perrašymo eilutės iki tol, kol susiduriate su dokumentais, kuriuos žmonės iš tikrųjų generuoja. Jei Delphi arba C++Builder dirbate su užpildytomis formomis, peržiūros žymėjimais ar archyvine išvestimi, verta perskaityti, kaip HotPDF Delphi komponentas tvarko įkelto dokumento AcroForm ir anotacijų pusę, prieš statant savo išvaizdos generatorių ant jos