Ko FPDFPage_TransFormWithClip prepiše stran, vsak ročnik FPDF_PAGEOBJECT, ki ga že imate, še vedno opisuje razčlenitev izpred transformacije. PDFium Component za Delphi in C++Builder to reši znotraj TransformPageContent, ki sprosti besedilno stran, regenerira vsebino in nato ponovno naloži stran, tako da poznejša poizvedovanja vidijo nove koordinate
Simptom je tih. Uporabite skalo 0,9, da dodate tiskarski rob, nato preberete PageObjectInfo in dobite natanko iste številke kot pred klicem. Brez izjeme, brez kode napake, nič v dnevniku. To je drugačna odpoved od predpomnjene besedilne strani, opisane v članku o zastareli besedilni strani po urejanju: tam je predpomnilnik en sam ročnik FPDF_TEXTPAGE, ki ga lahko izpustite in obnovite, tukaj pa je problem vsak ročnik objekta strani v vaših lastnih spremenljivkah, plus razred pridobivalnikov, ki poroča o odpovedi prek kode vrnitve, ki jo večina klicateljev zavrže
Zakaj meje objekta strani zastarijo brez napake?
Ker je ročnik objekta strani kazalec v razčlenjeno predstavitev enega določenega vsebinskega toka, transformacija cele strani pa ta vsebinski tok zamenja z novim. PDFium se ne sprehodi po vašem klicnem skladu in išče ročnike za popravek. Zgradi svež graf objektov in stari pusti natanko takšen, kakršen je bil, tako da je branje proti staremu ročniku povsem veljavno branje strukture, ki ne ustreza več temu, kar pravi datoteka
ISO 32000-1 §7.8.2 opredeljuje vsebinski tok kot zaporedje operatorjev, ki izriše stran, §8.3.3 pa opredeljuje, kako trenutna transformacijska matrika preslika uporabniški prostor v prostor naprave. Transformacija na ravni strani je izražena z ovijanjem in prepisovanjem teh operatorjev, ne z urejanjem koordinat posameznega objekta na mestu. Tako se koordinate, ki jih objekti nosijo, morda sploh ne spremenijo; kar se spremeni, je matrika v veljavi ob izrisu. Vsak ročnik, razčlenjen pod staro matriko, odgovarja na vprašanja geometrije pod staro matriko, in odgovarja brez pritožbe
Kaj FPDFPage_TransFormWithClip dejansko prepiše
Prepiše stran, ne vaših posnetkov. FPDFPage_TransFormWithClip vzame FS_MATRIX in obrezovalni pravokotnik FS_RECTF ter oba uporabi na celotno vsebino strani. To je pravi klic za robove, skaliranje razporeditve in normalizacijo nenavadno velike strani proti ciljni škatli. Napačen klic je, po katerem posegati, če pričakujete, da bodo obstoječi ročniki sledili, prav tako je vredno zapomniti, da se dotakne le vsebine strani: opombe so ločena plast in potrebujejo TransformPageAnnotations, ki posreduje istih šest koeficientov matrike FPDFPage_TransformAnnots
var
Info: TPdfPageObjectInfo;
Scale: FS_MATRIX;
Clip: TPdfRectangle;
begin
Pdf.PageNumber:= 1;
Info:= Pdf.PageObjectInfo(0); // snapshot taken before the transform
Scale.a:= 0.9; Scale.b:= 0.0;
Scale.c:= 0.0; Scale.d:= 0.9;
Scale.e:= 29.7; Scale.f:= 42.0; // 5% margin, A4 in points
Clip:= Pdf.GetPageBox(pbMedia);
Pdf.TransformPageContent(Scale, Clip);
// Info.Bounds still holds pre-transform geometry, and Info.Handle now
// points into a page that TransformPageContent has already replaced
end;
Vrstni red osvežitve, ki ga uporablja TransformPageContent
Štirje koraki, v tem vrstnem redu: sprosti besedilno stran, transformiraj, generiraj vsebino, ponovno naloži stran. TPdf.TransformPageContent izvede natanko to zaporedje. Pokliče CheckPageActive, kopira matriko in obrezovanje v njune izvorne oblike zapisa, pokliče UnloadTextPage, nato FPDFPage_TransFormWithClip, nato UpdatePage, ki je ovoj okoli FPDFPage_GenerateContent, in nazadnje ReloadPage
Vsak korak si zasluži svoje mesto. UnloadTextPage gre prvi, ker predpomnjen FPDF_TEXTPAGE nosi škatle znakov, izračunane pod staro matriko, prav tako pa opusti izpeljan seznam spletnih povezav in vsako iskalno sejo v teku, zgrajeno iz njega. FPDFPage_GenerateContent mora teči pred ponovnim nalaganjem, ker transformacija živi v strani v pomnilniku, dokler ni serializirana nazaj v vsebinski tok, ponovno nalaganje pa bi sicer ponovno razčlenilo nespremenjen tok. ReloadPage se zaključi s FPDF_LoadPage proti trenutnemu indeksu strani, kar je edina stvar, ki vam dejansko da svež graf objektov
// After the transform, re-enumerate. Do not reuse anything captured earlier.
var
I: Integer;
Info: TPdfPageObjectInfo;
begin
Pdf.TransformPageContent(Scale, Clip); // unload text page, transform,
// generate content, reload page
for I:= 0 to Pdf.ObjectCount- 1 do
begin
Info:= Pdf.PageObjectInfo(I); // handle and bounds from the new parse
if Info.Bounds.Right> PageWidth then
Log('object '+ IntToStr(I)+ ' still overflows after scaling');
end;
end;
Ena podrobnost v ReloadPage je vredna posnemanja, če to zaporedje kdaj napišete sami. Najprej naloži novo stran in jo šele nato zapiše v polje, tako da neuspešno nalaganje strani pusti trenutno izvorno stran in vse njene izpeljane predpomnilnike nedotaknjene namesto da bi vas pahnilo v napol podrto stanje. Ponovno nalaganje ni brezplačno — plačate za popolno ponovno razčlenitev strani — vendar je plačano enkrat na transformacijo, ne enkrat na poizvedbo, cenejše pravilne alternative pa ni
Ne nosite ročnikov čez ponovno nalaganje
Po ponovnem nalaganju stari ročniki niso le zastareli, temveč viseči. Prejšnja FPDF_PAGE je bila zaprta, vrednosti FPDF_PAGEOBJECT, ki so ji pripadale, pa so kazalci v sproščen pomnilnik. TPdfPageObjectInfo izpostavi izvorni ročnik v svojem polju Handle, kar je resnično uporabno za neposredno posredovanje objekta klicu nižje ravni, hkrati pa enako resnično nevarno za hranjenje v polju obrazca ali seznamu čez operacijo, ki ponovno naloži stran. Zapis posnetka obravnavajte kot veljaven le do naslednjega klica, ki regenerira vsebino, v istem duhu kot pravila lastništva, obravnavana v opombah o ABI in varnosti pomnilnika na meji PDFium
Ali lahko pridobivalnik odpove in je še vedno videti kot veljavni podatki?
Da, in to je druga polovica istega problema. FPDFPageObj_GetRotatedBounds in FPDFPageObj_GetIsActive sta pridobivalnika z izhodnimi parametri: vrneta zastavico uspeha int in dejanski odgovor zapišeta v referenčni argument. Oba lahko vrneta FALSE za objekt, ki je bil ustvarjen, katerega stran pa še ni bila ponovno razčlenjena. Ko se to zgodi, izhodni parameter ostane nedotaknjen, zapis Pascal, inicializiran z Default(TPdfPageObjectInfo), pa je vse ničle, tako da klicatelj vidi štirikotnik s štirimi točkami v izhodišču in zastavico Active False. Neuspel klic je bil tiho povišan v verjetno izgledajoče podatke
TPdfPageObjectInfo na to odgovori z izrecnimi varovalkami. HasRotatedBounds nosi rezultat klica FPDFPageObj_GetRotatedBounds, HasActiveState nosi rezultat FPDFPageObj_GetIsActive, polja geometrije in stanja pa se zapišejo le, kadar je ustrezna varovalka True. Ista oblika se ponovi čez zapis za druge pridobivalnike z izhodnimi parametri, tako da HasMatrix, HasFillColor, HasStrokeColor in HasStrokeWidth vsi pomenijo isto: izvorni klic je uspel in sosednje polje je pomembno
Info:= Pdf.PageObjectInfo(I);
if Info.HasRotatedBounds then
// RotatedBounds is array [1..4] of TPdfPoint, in draw order
UseQuad(Info.RotatedBounds[1], Info.RotatedBounds[2],
Info.RotatedBounds[3], Info.RotatedBounds[4])
else
// the native call failed; fall back to the axis-aligned rectangle
UseRect(Info.Bounds);
if Info.HasActiveState and (not Info.Active) then
SkipObject(I); // genuinely inactive
// if HasActiveState is False, the object state is unknown, not inactive
Vzorec se posploši na vsak pridobivalnik PDFium, ki sledi konvenciji kode vrnitve plus izhodnega parametra, teh pa je veliko. Če ovoj to konvencijo strne v navaden rezultat funkcije, je zavrgel edini signal, ki loči "odgovor je nič" od "odgovora ni". Nošenje ene dodatne logične vrednosti na polje stane bajt in odpravi celo kategorijo hrošča, kjer je privzet zapis zamenjan za meritev
Kje to še vedno grize
Tri iskrene meje. Prvič, osvežitev je na stran: transformirajte stran dve, na kateri koli ročnike, ki jih hranite za stran ena, to ne vpliva, imate pa zdaj dve strani, razčlenjeni ob različnih časih, na vas pa je, da si zapomnite, kateri posnetki so prišli s katere. Drugič, stabilnost indeksa ni zagotovljena čez regeneracijo vsebine — po ponovnem nalaganju je indeks 3 karkoli, kar je indeks 3 v novi razčlenitvi, zato objekte ponovno identificirajte po njihovem tipu in geometriji namesto da predpostavljate, da so se položaji obdržali. Tretjič, obrezovalni pravokotnik v FPDFPage_TransFormWithClip je uporabljen na vsebino strani in ne spremeni velikosti nobene od škatel strani; če vsebino skalirate navzdol, da ustvarite rob, je MediaBox še vedno enake velikosti, kot je vedno bil, pregledovalnik pa bo pokazal izvirni list z risbo, skrčeno znotraj njega. Nič od tega ni eksotično — je navadna posledica API-ja C, ki razdaja kazalce v razčlenjeno stanje in prepusti življenjsko dobo klicatelju. Popravek je tisti, ki deluje povsod drugod: natančno določite, kdaj posnetek poteče, osvežite na tej meji in nikoli ne pustite, da neuspel klic mimika vrednost
Če delate skozi obnašanje matrik bolj splošno, je vrstni red množenja, ki odloči, kam transformacija pristane, pokrit v članku o predpisu, dodajanju in vrtišču z matrikami. Tukaj opisana API-ja za transformacijo in objekt strani dostavlja PDFium Component za Delphi in C++Builder, katere stran izdelka nosi celoten referenčni pregled zapisa posnetka objekta strani in njegovih varovalnih polj