Kai FPDFPage_TransFormWithClip perrašo puslapį, kiekviena FPDF_PAGEOBJECT rankena, kurią jau turite, vis dar aprašo analizę iš prieš transformaciją. PDFium Component Delphi ir C++Builder platformoms tai sprendžia TransformPageContent viduje, kuri paleidžia teksto puslapį, atnaujina turinį, tada iš naujo įkelia puslapį, kad vėlesnės užklausos matytų naujas koordinates
Simptomas — tylus. Pritaikote 0.9 mastelį, kad pridėtumėte spausdinimo paraštę, tada skaitote PageObjectInfo ir gaunate lygiai tuos pačius skaičius, kuriuos gavote prieš iškvietimą. Jokios išimties, jokio klaidos kodo, nieko žurnale. Tai — kitoks gedimas nei podėlyje esantis teksto puslapis, aprašytas straipsnyje apie pasenusius teksto puslapius po redagavimo: ten podėlis — viena FPDF_TEXTPAGE rankena, kurią galite paleisti ir atkurti, čia problema — kiekviena puslapio objekto rankena jūsų pačių kintamuosiuose, plius getter'ių klasė, pranešanti apie nesėkmę per grąžinimo kodą, kurį dauguma iškviečiančiųjų išmeta
Kodėl puslapio objekto ribos pasensta be klaidos?
Todėl, kad puslapio objekto rankena — rodyklė į išanalizuotą vieno konkretaus turinio srauto vaizdą, o visas puslapio transformacija pakeičia tą turinio srautą nauju. PDFium neina per jūsų iškvietimų steką, ieškodama rankenų taisyti. Ji sukuria šviežią objektų grafą ir palieka senąjį lygiai toks, koks buvo, todėl skaitymas prieš seną rankeną — visiškai galiojantis struktūros, kuri nebeatitinka to, ką sako failas, skaitymas
ISO 32000-1 §7.8.2 apibrėžia turinio srautą kaip operatorių seką, piešiančią puslapį, o §8.3.3 apibrėžia, kaip dabartinė transformacijos matrica atvaizduoja vartotojo erdvę į įrenginio erdvę. Puslapio lygio transformacija išreiškiama apgaubiant ir perrašant tuos operatorius, ne redaguojant kiekvieno objekto koordinates vietoje. Todėl objektų nešamos koordinatės gali visai nepasikeisti; tai, kas keičiasi, — matrica, galiojanti piešimo metu. Bet kokia rankena, kuri buvo išanalizuota prie senos matricos, atsako į geometrijos klausimus prie senos matricos, ir atsako be jokio nusiskundimo
Ką iš tikrųjų perrašo FPDFPage_TransFormWithClip
Ji perrašo puslapį, ne jūsų nuotraukas. FPDFPage_TransFormWithClip ima FS_MATRIX ir FS_RECTF apkarpymo stačiakampį ir taiko abu visam puslapio turiniui. Tai — teisingas iškvietimas paraštėms, sudėties mastelio keitimui ir keisto dydžio puslapio normalizavimui pagal tikslinę dėžę. Tai — neteisingas iškvietimas, jei tikitės, kad esamos rankenos seks kartu, ir taip pat verta prisiminti, kad ji liečia tik puslapio turinį: anotacijos — atskiras sluoksnis, joms reikia TransformPageAnnotations, kuri persiunčia tuos pačius šešis matricos koeficientus 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;
Atnaujinimo tvarka, kurią naudoja TransformPageContent
Keturi žingsniai, šia tvarka: paleisti teksto puslapį, transformuoti, generuoti turinį, iš naujo įkelti puslapį. TPdf.TransformPageContent vykdo lygiai šią seką. Ji iškviečia CheckPageActive, kopijuoja matricą ir apkarpymą į savo natyvias įrašų formas, iškviečia UnloadTextPage, tada FPDFPage_TransFormWithClip, tada UpdatePage, kuri yra apvalkalas aplink FPDFPage_GenerateContent, ir galiausiai ReloadPage
Kiekvienas žingsnis pelnytai užima savo vietą. UnloadTextPage eina pirmas, nes podėlyje esantis FPDF_TEXTPAGE laiko simbolių dėžes, apskaičiuotas pagal seną matricą, ir jis taip pat paleidžia kildintą žiniatinklio nuorodų sąrašą ir bet kokį vykstantį paieškos seansą, sukurtą iš jo. FPDFPage_GenerateContent turi vykti prieš pakartotinį įkėlimą, nes transformacija gyvena atmintyje esančiame puslapyje, kol ji serializuojama atgal į turinio srautą, o pakartotinis įkėlimas kitaip iš naujo analizuotų nepakeistą srautą. ReloadPage baigiasi FPDF_LoadPage pagal dabartinį puslapio indeksą, kas — vienintelis dalykas, iš tikrųjų duodantis jums šviežią objektų grafą
// 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;
Viena detalė ReloadPage verta pasiskolinti, jei kada nors rašytumėte šią seką patys. Ji pirma įkelia naują puslapį ir tik po to jį patvirtina laukui, todėl nepavykęs puslapio įkėlimas palieka dabartinį natyvų puslapį ir visus jo kildintus podėlius nepaliestus, o ne numeta jus į pusiau sugriautą būseną. Pakartotinis įkėlimas — ne nemokamas, jūs mokate už pilną puslapio analizę iš naujo, bet apmokama vieną kartą transformacijai, ne kiekvienai užklausai, ir nėra pigesnės teisingos alternatyvos
Nenešiokite rankenų per pakartotinį įkėlimą
Po pakartotinio įkėlimo senosios rankenos ne tik pasenusios, jos — kabančios. Ankstesnis FPDF_PAGE uždarytas, o FPDF_PAGEOBJECT reikšmės, priklausiusios jam, — rodyklės į paleistą atmintį. TPdfPageObjectInfo atveria natyvią rankeną savo Handle lauke, kuris iš tikrųjų naudingas perduodant objektą tiesiai į žemesnio lygio iškvietimą, ir lygiai taip pat iš tikrųjų pavojingas laikyti formos lauke ar sąraše per operaciją, iš naujo įkeliančią puslapį. Traktuokite nuotraukos įrašą kaip galiojantį tik iki kito iškvietimo, kuris iš naujo generuoja turinį, ta pačia dvasia kaip nuosavybės taisyklės, aptartos straipsnyje apie ABI ir atminties saugumą prie PDFium ribos
Ar getter'is gali nepavykti ir vis tiek atrodyti kaip galiojantys duomenys?
Taip, ir tai — antra tos pačios problemos pusė. FPDFPageObj_GetRotatedBounds ir FPDFPageObj_GetIsActive — iš-parametro getter'iai: jie grąžina int sėkmės vėliavėlę ir įrašo tikrą atsakymą į nuorodos argumentą. Abu gali grąžinti FALSE objektui, kuris buvo sukurtas, bet kurio puslapis dar nebuvo iš naujo išanalizuotas. Kai tai nutinka, iš-parametras paliekamas nepaliestas, o Pascal įrašas, inicializuotas su Default(TPdfPageObjectInfo), yra visi nuliai, todėl iškviečiantysis mato keturkampį su keturiais taškais pradžios taške ir Active vėliavėlę, lygią False. Nepavykęs iškvietimas tyliai paverstas tikėtinai atrodančiais duomenimis
TPdfPageObjectInfo į tai atsako aiškiais sargybiniais. HasRotatedBounds neša FPDFPageObj_GetRotatedBounds iškvietimo rezultatą, HasActiveState neša FPDFPageObj_GetIsActive rezultatą, o geometrijos ir būsenos laukai rašomi tik tada, kai atitinkamas sargybinis — True. Ta pati forma kartojasi visame įraše kitiems iš-parametro getter'iams, todėl HasMatrix, HasFillColor, HasStrokeColor ir HasStrokeWidth visi reiškia tą patį: natyvus iškvietimas pavyko, o šalia esantis laukas — prasmingas
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
Šis raštas apibendrinamas kiekvienam PDFium getter'iui, sekančiam grąžinimo-kodo-plius-iš-parametro konvenciją, o tokių — daug. Jei apvalkalas suplokština tą konvenciją į paprastą funkcijos rezultatą, jis prarado vienintelį signalą, atskiriantį „atsakymas — nulis“ nuo „atsakymo nėra“. Vieno papildomo loginio kintamojo laikymas kiekvienam laukui kainuoja baitą ir pašalina visą klaidų kategoriją, kurioje numatytas įrašas suklaidinamas kaip matavimas
Kur tai vis dar kanda
Trys sąžiningos ribos. Pirma, atnaujinimas — vieno puslapio: transformuokite antrą puslapį, ir bet kokios rankenos, kurias laikote pirmam puslapiui, nepaliečiamos, bet dabar turite du puslapius, išanalizuotus skirtingu metu, ir jūsų reikalas — prisiminti, kurios nuotraukos iš kurio kilo. Antra, indekso stabilumas negarantuojamas per turinio regeneraciją — po pakartotinio įkėlimo indeksas 3 yra tai, kas yra indeksas 3 naujoje analizėje, todėl iš naujo identifikuokite objektus pagal jų tipą ir geometriją, o ne manydami, kad pozicijos išsilaikė. Trečia, apkarpymo stačiakampis FPDFPage_TransFormWithClip taikomas puslapio turiniui ir nepakeičia dydžio jokiai puslapio dėžei; jei sumažinate turinį, kad sukurtumėte paraštę, MediaBox vis tiek tokio dydžio, koks visada buvo, o žiūryklė parodys originalų lapą su sutrauktu brėžiniu jo viduje. Nieko iš to nėra egzotiška — tai įprasta pasekmė C API, dalinančio rodykles į išanalizuotą būseną ir paliekančio gyvavimo trukmę iškviečiančiajam. Pataisymas — toks, kuris veikia visur kitur: apibrėžkite tiksliai, kada nuotrauka nebegalioja, atnaujinkite prie tos ribos ir niekada neleiskite nepavykusiam iškvietimui apsimesti reikšme
Jei nagrinėjate matricos elgesį plačiau, daugybos tvarka, nusprendžianti, kur atsiduria transformacija, aprašyta straipsnyje apie prepend, append ir pivot su matricomis. Transformacijos ir puslapio objekto API, aprašytos čia, dalyvauja PDFium Component Delphi ir C++Builder platformoms, kurio produkto puslapyje pateikta pilna puslapio objekto nuotraukos įrašo ir jo sargybinių laukų dokumentacija