Kun FPDFPage_TransFormWithClip kirjoittaa sivun uudelleen, jokainen FPDF_PAGEOBJECT-kahva, joka sinulla jo on, kuvaa yhä jäsennystä ennen muunnosta. PDFium Component Delphille ja C++Builderille ratkaisee tämän sisäisesti TransformPageContent:ssa, joka poistaa tekstisivun lataamisesta, regeneroi sisällön ja lataa sitten sivun uudelleen, jotta myöhemmät kyselyt näkevät uudet koordinaatit
Oire on hiljainen. Sovellat 0,9 skaalausta lisätäksesi tulostusmarginaalin, sitten luet PageObjectInfo:n ja saat täsmälleen samat luvut kuin ennen kutsua. Ei poikkeusta, ei virhekoodia, ei mitään lokissa. Tämä on eri vika kuin välimuistitettu tekstisivu, joka on kuvattu artikkelissa vanhentunut tekstisivu muokkauksen jälkeen: siellä välimuisti on yksi FPDF_TEXTPAGE-kahva, jonka voit pudottaa ja rakentaa uudelleen, täällä ongelma on jokainen sivuobjektikahva omissa muuttujissasi, plus hakijaluokka, joka raportoi epäonnistumisen paluukoodilla, jonka useimmat kutsujat heittävät pois
Miksi sivuobjektin rajat vanhenevat ilman virhettä?
Koska sivuobjektikahva on osoitin yhden tietyn sisältövirran jäsennettyyn esitykseen, ja koko sivun muunnos korvaa tuon sisältövirran uudella. PDFium ei kävele kutsupinoasi läpi etsien kahvoja paikattavaksi. Se rakentaa tuoreen objektigraafin ja jättää vanhan täsmälleen ennalleen, joten luku vanhaa kahvaa vasten on täysin kelvollinen luku rakenteesta, joka ei enää vastaa sitä, mitä tiedosto sanoo
ISO 32000-1 §7.8.2 määrittelee sisältövirran operaattorien sekvenssinä, joka piirtää sivun, ja §8.3.3 määrittelee, miten nykyinen muunnosmatriisi kuvaa käyttäjäavaruuden laiteavaruuteen. Sivutason muunnos ilmaistaan kääriytymällä ja kirjoittamalla nuo operaattorit uudelleen, ei muokkaamalla objektikohtaisia koordinaatteja paikallaan. Joten koordinaatit, joita objektit kantavat, eivät ehkä muutu lainkaan; se, mikä muuttuu, on matriisi, joka on voimassa niitä piirrettäessä. Mikä tahansa kahva, joka jäsennettiin vanhan matriisin alaisena, vastaa geometriakysymyksiin vanhan matriisin alaisena, ja vastaa niihin ilman valitusta
Mitä FPDFPage_TransFormWithClip todella kirjoittaa uudelleen
Se kirjoittaa uudelleen sivun, ei tilannekuviasi. FPDFPage_TransFormWithClip ottaa FS_MATRIX:n ja FS_RECTF-leikkaussuorakulmion ja soveltaa molempia koko sivun sisältöön. Se on oikea kutsu marginaaleille, taittoskaalaukselle ja oudonkokoisen sivun normalisoinnille kohdelaatikkoa vasten. Se on väärä kutsu, jos odotat olemassa olevien kahvojen seuraavan mukana, ja kannattaa myös muistaa, että se koskettaa vain sivun sisältöä: merkinnät ovat erillinen kerros ja tarvitsevat TransformPageAnnotations:n, joka välittää samat kuusi matriisikerrointa FPDFPage_TransformAnnots:lle
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;
Päivitysjärjestys, jota TransformPageContent käyttää
Neljä vaihetta, tässä järjestyksessä: poista tekstisivun lataus, muunna, generoi sisältö, lataa sivu uudelleen. TPdf.TransformPageContent ajaa täsmälleen tuon sekvenssin. Se kutsuu CheckPageActive:a, kopioi matriisin ja leikkauksen niiden natiiveihin tietuemuotoihin, kutsuu UnloadTextPage:a, sitten FPDFPage_TransFormWithClip:tä, sitten UpdatePage:a, joka on kääre FPDFPage_GenerateContent:n ympärillä, ja lopuksi ReloadPage:a
Jokainen vaihe ansaitsee paikkansa. UnloadTextPage menee ensin, koska välimuistitettu FPDF_TEXTPAGE pitää sisällään merkkilaatikoita laskettuna vanhan matriisin alaisena, ja se myös pudottaa johdetun weblink-listan ja minkä tahansa käynnissä olevan hakusession, joka rakennettiin siitä. FPDFPage_GenerateContent:n täytyy ajaa ennen uudelleenlatausta, koska muunnos asuu muistissa olevassa sivussa, kunnes se serialisoidaan takaisin sisältövirtaan, ja uudelleenlataus muuten jäsentäisi uudelleen muokkaamattoman virran. ReloadPage päättyy FPDF_LoadPage:iin nykyistä sivuindeksiä vasten, mikä on ainoa asia, joka todella antaa sinulle tuoreen objektigraafin
// 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;
Yksi yksityiskohta ReloadPage:ssa kannattaa omaksua, jos joskus kirjoitat tämän sekvenssin itse. Se lataa uuden sivun ensin ja sitoutuu siihen vasta jälkikäteen kenttään, joten sivun lataus, joka epäonnistuu, jättää nykyisen natiivin sivun ja kaikki sen johdetut välimuistit koskemattomiksi sen sijaan, että pudottaisi sinut puoliksi purettuun tilaan. Uudelleenlataus ei ole ilmainen — maksat sivun täydestä uudelleenjäsennyksestä — mutta se maksetaan kerran per muunnos, ei kerran per kysely, eikä halvempaa oikeaa vaihtoehtoa ole
Älä kanna kahvoja uudelleenlatauksen yli
Uudelleenlatauksen jälkeen vanhat kahvat eivät ole vain vanhentuneita, ne roikkuvat tyhjinä. Edellinen FPDF_PAGE on suljettu, ja FPDF_PAGEOBJECT-arvot, jotka kuuluivat sille, ovat osoittimia vapautettuun muistiin. TPdfPageObjectInfo paljastaa natiivin kahvan Handle-kentässään, mikä on aidosti hyödyllistä objektin antamiseen suoraan matalamman tason kutsuun, ja yhtä aidosti vaarallista pitää lomakekentässä tai listassa operaation yli, joka lataa sivun uudelleen. Kohtele tilannekuvatietuetta kelvollisena vain seuraavaan sisällön regeneroivaan kutsuun asti, samassa hengessä kuin omistusäännöt, joita käsitellään artikkelissa ABI ja muistiturvallisuus PDFium-rajalla
Voiko hakija epäonnistua ja silti näyttää kelvolliselta datalta?
Kyllä, ja tämä on saman ongelman toinen puolisko. FPDFPageObj_GetRotatedBounds ja FPDFPageObj_GetIsActive ovat ulosparametri-hakijoita: ne palauttavat int-onnistumislipun ja kirjoittavat todellisen vastauksen viittausargumenttiin. Molemmat voivat palauttaa FALSE:n objektille, joka luotiin mutta jonka sivua ei ole vielä jäsennetty uudelleen. Kun näin tapahtuu, ulosparametri jätetään koskemattomaksi, ja Pascal-tietue, joka on alustettu Default(TPdfPageObjectInfo):lla, on kokonaan nollia, joten kutsuja näkee nelikulmion, jossa on neljä pistettä origossa, ja Active-lipun arvolla False. Epäonnistunut kutsu on hiljaa korotettu uskottavan näköiseksi dataksi
TPdfPageObjectInfo vastaa tähän nimenomaisilla vartijoilla. HasRotatedBounds kantaa FPDFPageObj_GetRotatedBounds-kutsun tuloksen, HasActiveState kantaa FPDFPageObj_GetIsActive:n tuloksen, ja geometria- ja tilakentät kirjoitetaan vain, kun vastaava vartija on True. Sama muoto toistuu tietueen yli muille ulosparametri-hakijoille, joten HasMatrix, HasFillColor, HasStrokeColor ja HasStrokeWidth tarkoittavat kaikki samaa asiaa: natiivi kutsu onnistui ja naapurikenttä on merkityksellinen
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
Kuvio yleistyy jokaiseen PDFium-hakijaan, joka noudattaa paluukoodi-plus-ulosparametri-käytäntöä, ja niitä on paljon. Jos kääre supistaa tuon käytännön pelkäksi funktion paluuarvoksi, se on heittänyt pois ainoan signaalin, joka erottaa "vastaus on nolla" ja "vastausta ei ole". Yhden ylimääräisen totuusarvon kantaminen per kenttä maksaa tavun ja poistaa kokonaisen vikaluokan, jossa oletustietue erehdytään mittaukseksi
Missä tämä yhä puree
Kolme rehellistä rajaa. Ensinnäkin päivitys on sivukohtainen: sivun kaksi muuntaminen ei vaikuta mihinkään kahvoihin, joita pidät sivulle yksi, mutta sinulla on nyt kaksi sivua jäsennettynä eri aikoina, ja sinun on muistettava, mikä tilannekuva tuli mistäkin. Toiseksi indeksin vakautta ei taata sisällön regeneroinnin yli — uudelleenlatauksen jälkeen indeksi 3 on mitä tahansa indeksi 3 on uudessa jäsennyksessä, joten tunnista objektit uudelleen niiden tyypin ja geometrian perusteella sen sijaan, että olettaisit positioiden pysyvän. Kolmanneksi leikkaussuorakulmio FPDFPage_TransFormWithClip:ssä sovelletaan sivun sisältöön eikä muuta minkään sivulaatikon kokoa; jos skaalaat sisältöä pienemmäksi luodaksesi marginaalin, MediaBox on yhä sen kokoinen kuin aina, ja katseluohjelma näyttää alkuperäisen arkin, jonka sisällä piirros on kutistunut. Mikään tästä ei ole eksoottista — se on tavallinen seuraus C-API:sta, joka jakaa osoittimia jäsennettyyn tilaan ja jättää elinajan kutsujalle. Korjaus on se, joka toimii kaikkialla muuallakin: määrittele täsmälleen, milloin tilannekuva vanhenee, päivitä tuolla rajalla, äläkä koskaan anna epäonnistuneen kutsun naamioitua arvoksi
Jos työstät matriisikäyttäytymistä yleisemmin, kertolaskujärjestys, joka päättää, minne muunnos päätyy, käsitellään artikkelissa prepend, append ja pivot matriisien kanssa. Tässä kuvatut muunnos- ja sivuobjekti-API:t toimitetaan PDFium Componentin mukana Delphille ja C++Builderille, jonka tuotesivu kantaa täyden viitteen sivuobjektin tilannekuvatietueelle ja sen vartijakentille