Kad FPDFPage_TransFormWithClip prepiše stranicu, svaki rukovatelj FPDF_PAGEOBJECT koji već držite i dalje opisuje raščlambu prije transformacije. PDFium Component za Delphi i C++Builder ovo rješava unutar TransformPageContent, koji rasterećuje stranicu teksta, regenerira sadržaj, zatim ponovno učitava stranicu tako da kasniji upiti vide nove koordinate
Simptom je tih. Primijenite skaliranje 0,9 da dodate margine za ispis, zatim pročitate PageObjectInfo i dobijete točno iste brojke koje ste dobili prije poziva. Nema iznimke, nema koda greške, ničega u zapisniku. Ovo je drugačiji neuspjeh od predmemorirane stranice teksta opisane u članku o zastarjelim stranicama teksta nakon izmjene: ondje je predmemorija jedan rukovatelj FPDF_TEXTPAGE koji možete ispustiti i ponovno izgraditi, ovdje je problem svaki rukovatelj objekta stranice u vašim vlastitim varijablama, plus klasa getter-a koji izvještavaju neuspjeh kroz povratni kod koji većina pozivatelja odbaci
Zašto granice objekta stranice zastarijevaju bez greške?
Zato što je rukovatelj objekta stranice pokazivač u raščlanjeni prikaz jednog određenog sadržajnog streama, a transformacija cijele stranice zamjenjuje taj sadržajni stream novim. PDFium ne prolazi kroz vaš stog poziva tražeći rukovatelje za zakrpu. Gradi svjež graf objekata i ostavlja stari upravo onakvim kakav je bio, tako da je čitanje protiv starog rukovatelja savršeno valjano čitanje strukture koja više ne odgovara onome što datoteka kaže
ISO 32000-1 §7.8.2 definira sadržajni stream kao slijed operatora koji crta stranicu, a §8.3.3 definira kako trenutna transformacijska matrica mapira korisnički prostor na prostor uređaja. Transformacija na razini stranice izražava se omotavanjem i prepisivanjem tih operatora, a ne izmjenom koordinata po objektu na mjestu. Tako se koordinate koje objekti nose možda uopće ne mijenjaju; ono što se mijenja je matrica na snazi kad se crtaju. Svaki rukovatelj koji je raščlanjen pod starom matricom odgovara na pitanja o geometriji pod starom matricom, i odgovara bez prigovora
Što FPDFPage_TransFormWithClip zapravo prepisuje
Prepisuje stranicu, ne vaše snimke. FPDFPage_TransFormWithClip uzima FS_MATRIX i pravokutnik clipa FS_RECTF i primjenjuje oboje na cijeli sadržaj stranice. To je ispravan poziv za margine, skaliranje impozicije i normaliziranje čudno dimenzionirane stranice protiv ciljnog okvira. Pogrešan je poziv po koji posegnuti ako očekujete da postojeći rukovatelji prate promjenu, i također vrijedi zapamtiti da dira samo sadržaj stranice: napomene su odvojen sloj i trebaju TransformPageAnnotations, koji prosljeđuje istih šest koeficijenata matrice u 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;
Redoslijed osvježavanja koji koristi TransformPageContent
Četiri koraka, ovim redoslijedom: rasteretiti stranicu teksta, transformirati, generirati sadržaj, ponovno učitati stranicu. TPdf.TransformPageContent izvodi upravo taj slijed. Poziva CheckPageActive, kopira matricu i clip u njihove izvorne oblike zapisa, poziva UnloadTextPage, zatim FPDFPage_TransFormWithClip, zatim UpdatePage, koji je omotač oko FPDFPage_GenerateContent, i naposljetku ReloadPage
Svaki korak zaslužuje svoje mjesto. UnloadTextPage ide prvi jer predmemorirani FPDF_TEXTPAGE drži okvire znakova izračunate pod starom matricom, a također ispušta izveden popis web poveznica i svaku find sesiju u tijeku koje su izgrađene iz njega. FPDFPage_GenerateContent mora se izvesti prije ponovnog učitavanja, jer transformacija živi u stranici u memoriji dok se ne serijalizira natrag u sadržajni stream, a ponovno učitavanje bi inače ponovno raščlanilo nepromijenjeni stream. ReloadPage zatvara s FPDF_LoadPage protiv trenutnog indeksa stranice, što je jedino što vam zapravo daje svjež graf objekata
// 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;
Jedan detalj u ReloadPage vrijedi kopirati ako ikad sami napišete ovaj slijed. Prvo učitava novu stranicu i tek je nakon toga predaje polju, tako da neuspjelo učitavanje stranice ostavlja trenutnu izvornu stranicu i sve njene izvedene predmemorije netaknutima umjesto da vas baci u polusrušeno stanje. Ponovno učitavanje nije besplatno — plaćate za punu ponovnu raščlambu stranice — ali plaća se jednom po transformaciji, ne jednom po upitu, i nema jeftinije ispravne alternative
Ne nosite rukovatelje preko ponovnog učitavanja
Nakon ponovnog učitavanja, stari rukovatelji nisu samo zastarjeli, oni vise. Prethodni FPDF_PAGE je zatvoren, a vrijednosti FPDF_PAGEOBJECT koje su mu pripadale pokazivači su u oslobođenu memoriju. TPdfPageObjectInfo izlaže izvorni rukovatelj u svom polju Handle, što je istinski korisno za izravno prosljeđivanje objekta u poziv niže razine, i podjednako istinski opasno za čuvanje u polju obrasca ili popisu preko operacije koja ponovno učitava stranicu. Tretirajte zapis snimke kao valjan samo do sljedećeg poziva koji regenerira sadržaj, u istom duhu kao pravila vlasništva razmotrena u bilješkama o ABI-ju i sigurnosti memorije na PDFium granici
Može li getter propasti, a i dalje izgledati kao valjani podaci?
Da, i ovo je druga polovica istog problema. FPDFPageObj_GetRotatedBounds i FPDFPageObj_GetIsActive su getter-i s izlaznim parametrima: vraćaju zastavicu uspjeha tipa int i pišu pravi odgovor u referentni argument. Oba mogu vratiti FALSE za objekt koji je stvoren, ali čija stranica još nije ponovno raščlanjena. Kad se to dogodi, izlazni parametar ostaje nedirnut, a Pascal zapis inicijaliziran s Default(TPdfPageObjectInfo) je posve nule, tako da pozivatelj vidi četverokut s četiri točke u ishodištu i zastavicu Active od False. Neuspjeli poziv tiho je promoviran u uvjerljivo izgledajuće podatke
TPdfPageObjectInfo odgovara na to eksplicitnim sentinelima. HasRotatedBounds nosi rezultat poziva FPDFPageObj_GetRotatedBounds, HasActiveState nosi rezultat FPDFPageObj_GetIsActive, a polja geometrije i stanja pišu se samo kad je pripadajući sentinel True. Isti se oblik ponavlja kroz zapis za ostale getter-e s izlaznim parametrima, tako da HasMatrix, HasFillColor, HasStrokeColor, i HasStrokeWidth svi znače istu stvar: izvorni poziv uspio je, a susjedno je polje smisleno
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
Obrazac se generalizira na svaki PDFium getter koji slijedi konvenciju povratnog-koda-plus-izlaznog-parametra, a ima ih mnogo. Ako omotač sažme tu konvenciju u obični rezultat funkcije, odbacio je jedini signal koji razlikuje "odgovor je nula" od "odgovora nema". Nošenje jednog dodatnog buleana po polju košta bajt, a uklanja cijelu kategoriju greške gdje se zapis sa zadanim vrijednostima pogrešno protumači kao mjerenje
Gdje ovo i dalje grize
Tri iskrene granice. Prvo, osvježavanje je po stranici: transformirajte stranicu dva, a rukovatelji koje držite za stranicu jedan nisu pogođeni, ali sad imate dvije stranice raščlanjene u različitim trenucima, i na vama je da zapamtite koje su snimke iz koje. Drugo, stabilnost indeksa nije zajamčena preko regeneracije sadržaja — nakon ponovnog učitavanja, indeks 3 je što god je indeks 3 u novoj raščlambi, pa ponovno identificirajte objekte po njihovom tipu i geometriji umjesto pretpostavljanja da su pozicije održane. Treće, pravokutnik clipa u FPDFPage_TransFormWithClip primjenjuje se na sadržaj stranice i ne mijenja veličinu nijednog okvira stranice; ako smanjite sadržaj radi stvaranja margine, MediaBox je i dalje veličine kakve je uvijek bio, a preglednik će prikazati izvorni list sa crtežom smanjenim unutra. Ništa od ovoga nije egzotično — to je obična posljedica C API-ja koji dijeli pokazivače u raščlanjeno stanje i ostavlja trajanje pozivatelju. Popravak je onaj koji radi posvuda drugdje: definirajte točno kad snimka istječe, osvježite na toj granici, i nikad ne dopustite da se neuspjeli poziv prerušava u vrijednost
Ako radite kroz ponašanje matrica općenitije, redoslijed množenja koji odlučuje gdje transformacija slijeće pokriven je u članku o prepend-u, append-u i pivotu s matricama. Transformacija i API objekata stranice opisani ovdje isporučuju se s PDFium Component za Delphi i C++Builder, čija stranica proizvoda nosi punu referencu za zapis snimke objekta stranice i njegova polja sentinela