Funkcija FPDF_RenderPageBitmap v PDFium Component sprejme argument rotate, ki ga PDFium vedno prišteje k vrtenju, ki ga stran že vsebuje v lastnem vnosu /Rotate, zato branje shranjenega vrtenja strani in vračanje iste vrednosti v klic upodabljanja stran zavrti dvakrat. Enaka napaka se pokaže pri izračunu prilagajanja povečave: določanje velikosti sličice iz širine in višine strani pred vrtenjem ustvari napačno razmerje stranic, kadar je /Rotate 90 ali 270 stopinj, saj ima upodobljena bitna slika zamenjani širino in višino
Napako je po tem, ko veste, kaj iskati, lahko opaziti, prej pa jo je prav tako lahko spregledati. Prispe serija optično prebranih računov z mešanico pokončnih in ležečih izvirnikov, nekdo pred arhiviranjem polovico poravna z vrtenjem za 90 stopinj v Acrobatu, vrstica sličic v pregledovalniku Delphi, zgrajenem na PDFium, pa te strani prikaže postrani, obrnjene na glavo ali stisnjene v polje z napačno usmerjenostjo. Nobena izjema se ne sproži. Nobena napaka se ne zabeleži. Piksli so preprosto napačni, in to le pri podmnožici strani, ki jih je nekdo zavrtel naknadno — prav takšna napaka lahko preživi celoten preizkus QA z nezasukanim testnim PDF-jem, nato pa se pojavi v produkciji na 47. strani pravega dokumenta
Zakaj PDFium stran zavrti dvakrat?
PDFium pri vsakem upodabljanju bitne slike samodejno uporabi lastno vrednost /Rotate strani, ne glede na to, kaj je posredovano upodabljalniku. Parameter rotate funkcije FPDF_RenderPageBitmap, ki je v PDFiumPas izpostavljen kot vrednosti TRotation ro0, ro90, ro180 in ro270 na TPdf.RenderPage, TPdf.RenderTile ter TPdf.RenderPageThumbnail, ne določa končnega kota strani; določa dodatno vrtenje, ki se naloži na tisto, kar že določa slovar strani, zato je privzeta vrednost vseh teh metod ro0
TPdf.PageRotation prebere isto vrednost /Rotate prek FPDFPage_GetRotation, programska koda pa jo pogosto potrebuje iz razlogov, ki niso povezani z upodabljanjem, na primer za določanje razporeditve opombe v prostoru strani. Zanka je v eni sami vrstici: posredovanje PageRotation v argument Rotation metode RenderPage v pričakovanju, da bo klic stran postavil pokonci. Stran, shranjena z /Rotate 90, je v vsakem skladnem pregledovalniku, tudi v PDFium, pravilno prikazana zasukano; če ji dodate še ro90, se zavrti na 180 stopinj namesto na želenih 90, stran brez vrtenja pa se brez razloga zavrti za četrt obrata
// Wrong: PageRotation already reflects /Rotate, and PDFium applies
// it automatically on every render -- passing it again as Rotation
// doubles the angle
Bitmap := Pdf.RenderPage(0, 0, TargetW, TargetH, Pdf.PageRotation, []);
// Right: leave Rotation at its ro0 default and let PDFium apply the
// page's own /Rotate exactly once
Bitmap := Pdf.RenderPage(0, 0, TargetW, TargetH, ro0, []);
Čemu je parameter Rotation dejansko namenjen
Parameter Rotation ima v API-ju svoje mesto zaradi povsem drugačne naloge: dodajanja vrtenja, ki velja samo za pogled in ni povezano s shranjeno usmerjenostjo strani, kakršnega uporabi gumb orodne vrstice za vrtenje pogleda, ne da bi spremenil izvorno datoteko. TPdfView ima zato ta pojma kot ločeni lastnosti. TPdfView.PageRotation odraža lastni /Rotate strani in lahko prek FPDFPage_SetRotation v dokument zapiše novo vrednost; TPdfView.Rotation je začasna lastnost samo za pogled, privzeto nastavljena na ro0, ki se datoteke nikoli ne dotakne. Branje prve lastnosti in zapis v drugo je celotna napaka v enem stavku
// View-only: rotates what the user sees, changes nothing in the file
procedure TViewerForm.RotateViewClick(Sender: TObject);
begin
case PdfView.Rotation of
ro0: PdfView.Rotation := ro90;
ro90: PdfView.Rotation := ro180;
ro180: PdfView.Rotation := ro270;
ro270: PdfView.Rotation := ro0;
end;
end;
// Persistent: rewrites the page's own /Rotate entry in the document
procedure TViewerForm.RotatePageClick(Sender: TObject);
begin
case PdfView.PageRotation of
ro0: PdfView.PageRotation := ro90;
ro90: PdfView.PageRotation := ro180;
ro180: PdfView.PageRotation := ro270;
ro270: PdfView.PageRotation := ro0;
end;
end;
Zakaj se določanje velikosti pri prilagajanju povečave pokvari na enak način?
Določanje velikosti pri prilagajanju povečave se pokvari iz zrcalno nasprotnega razloga: izračun se začne z napačnim parom števil, ne z napačnim kotom. Običajen način določanja velikosti polja sličice od PDFium zahteva širino in višino strani, primerja njuno razmerje stranic z razpoložljivim poljem in izračuna največji pravokotnik, ki se vanj prilega — pri nezasukani strani to deluje brez težav. Isti izračun tiho odpove pri strani z /Rotate 90 ali /Rotate 270, ko širina in višina izvirata iz klica, ki poroča o izvorni velikosti strani pred vrtenjem: pokončna stran A4 z /Rotate 90 še vedno poroča približno 595 krat 842 točk, čeprav jo PDFium po uveljavitvi vrtenja pravilno upodobi približno kot 842 krat 595, zato je polje, izračunano iz para pred vrtenjem, povsem napačno usmerjeno
FPDF_GetPageSizeByIndex je konkreten primer klica, ki po zasnovi poroča o tej izvorni velikosti pred vrtenjem, zato je priročen za pregled mer strani brez nalaganja vsake strani, vendar tvegan za izračun prilagajanja povečave, ki tega ne upošteva. Popravek neposredno sledi opredelitvi težave: pred izračunom prilagoditve preverite vrtenje strani, pri vrtenju za 90 ali 270 stopinj zamenjajte širino in višino, izračunajte prilagojeno polje iz zamenjanega para in klicu upodabljanja še vedno posredujte ro0, saj PDFium ostaja tisti, ki uporabi dejansko vrtenje
Pravilne sličice brez ponovnega izumljanja izračuna prilagoditve
TPdf.RenderPageThumbnail ta popravek že vsebuje, zato je najkrajša pot do pravilne sličice klic te metode namesto ročnega sestavljanja logike prilagoditve in vrtenja. Za indeks strani, ki se začne z 1, ter največjo širino in višino RenderPageThumbnail izračuna prilagojeno polje, ga interno popravi za /Rotate 90 ali 270 in vrne bitno sliko v lastništvo klicatelja, ne da bi spremenil trenutno stran dokumenta ali sprožil dogodek OnPageChange — to je pomembno za vrstico sličic, zgrajeno ob živem pregledovalniku na istem primerku TPdf
// PageW, PageH are a page's own (unrotated) dimensions in points, for
// example from FPDF_GetPageSizeByIndex, which reports size before
// /Rotate is applied
function FitBox(PageW, PageH: Double; Rotation: TRotation;
MaxW, MaxH: Integer; out FitW, FitH: Integer): Boolean;
var
PgW, PgH, Swap: Integer;
begin
PgW := Round(PageW);
PgH := Round(PageH);
if PgW < 1 then PgW := 1;
if PgH < 1 then PgH := 1;
if Rotation in [ro90, ro270] then
begin
Swap := PgW;
PgW := PgH;
PgH := Swap;
end;
Result := (MaxW > 0) and (MaxH > 0);
if not Result then
Exit;
if PgW * MaxH > PgH * MaxW then
begin
FitW := MaxW;
FitH := (MaxW * PgH) div PgW;
end
else
begin
FitH := MaxH;
FitW := (MaxH * PgW) div PgH;
end;
end;
Pomožni funkciji FitBox se je vseeno vredno obdržati, saj RenderPageThumbnail pokriva le primer ene bitne slike. Mreža sličic po meri, trak za predogled tiskanja ali pogovorno okno za izbiro strani, ki več strani razporedi v neodvisna polja, potrebuje enak izračun prilagoditve, ki upošteva vrtenje, vendar morda ne potrebuje nove bitne slike za vsako ploščico, lastna načina povečave fit-page in fit-width v TPdfView pa se interno opirata na isto zamisel: pred primerjavo z razpoložljivim odjemalskim območjem glede na trenutno vrtenje pogleda izbereta širino ali višino strani za izračun razmerja povečave. Če sta zmogljivost povečave in drsenja v takem pregledovalniku naslednji težavi na seznamu, povezani članek o predpomnjenju upodabljanja in gladki povečavi v pregledovalniku Delphi na osnovi PDFium nadaljuje tam, kjer se pravilno določanje velikosti konča
Kako opaziti dvojno vrtenje, preden ga opazi stranka
Dvojno vrtenje ima zanesljiv vizualni znak: stran, ki je bila pri vnosu zavrtena za 90 stopinj, je glede na preostali dokument videti zavrtena za 180 stopinj, ne za 90, ker se je dodatni ro90 naložil na lastni ro90 strani namesto da bi ga nadomestil. Testni primer, sestavljen samo iz strani z /Rotate 0, tega nikoli ne bo odkril, saj je ro0, dodan k ro0, še vedno ro0 in napaka ostane nevidna; preizkusni primer mora imeti vsaj eno stran, shranjeno z /Rotate 90, in eno z /Rotate 270, preden lahko zaupate poti kode za sličice ali prilagojeno povečavo
Osnovni cevovod od strani do bitne slike, opisan v članku upodabljanje strani PDF v JPEG s PDFium Component, že pravilno upodobi zasukane strani brez posebne kode, prav zato, ker Rotation pusti na privzeti vrednosti ro0 in prepusti PDFium uporabo /Rotate. Napaka dvojnega vrtenja se pojavi šele, ko začne programska koda prebirati PageRotation in ga posredovati tja, kamor ne sodi
Tu opisani klici upodabljanja, ki upoštevajo vrtenje, in določanje velikosti sličic so del PDFium Component za Delphi in C++Builder, skupaj z drugimi API-ji za upodabljanje, ogled in pridobivanje besedila, zgrajenimi na istih razredih TPdf in TPdfView