Fungsi FPDF_RenderPageBitmap milik PDFium Component menerima sebuah argumen rotate yang selalu ditambahkan PDFium di atas rotasi apa pun yang sudah dibawa halaman itu dalam entry /Rotate-nya sendiri, sehingga membaca rotasi tersimpan sebuah halaman dan memasukkan kembali nilai yang sama itu ke pemanggilan render memutar halaman tersebut dua kali. Kesalahan yang identik muncul dalam matematika fit-zoom: mengukur sebuah thumbnail dari lebar dan tinggi tak-terotasi milik halaman menghasilkan aspect ratio yang salah kapan pun /Rotate adalah 90 atau 270 derajat, karena bitmap yang dirender keluar dengan lebar dan tinggi tertukar
Kegagalan itu mudah terlihat begitu Anda tahu apa yang dicari, dan mudah terlewat sebelum itu. Sebuah batch invoice hasil scan tiba dengan campuran orisinal potret dan lanskap, seseorang meluruskan setengahnya dengan sebuah rotasi 90-derajat di Acrobat sebelum mengarsipkannya, dan strip thumbnail dalam sebuah viewer Delphi yang dibangun di atas PDFium merender halaman-halaman tertentu itu miring, terbalik, atau terjepit ke dalam sebuah kotak berbentuk untuk orientasi yang salah. Tidak ada yang memunculkan exception. Tidak ada yang mencatat error. Piksel-piksel itu sekadar salah, dan hanya untuk subset halaman yang dirotasi seseorang setelah fakta — persis jenis bug yang bertahan dari langkah QA lengkap terhadap sebuah PDF uji tak-terotasi dan kemudian muncul dalam produksi di halaman 47 sebuah PDF sungguhan
Mengapa PDFium memutar halaman dua kali?
PDFium menerapkan nilai /Rotate sebuah halaman secara otomatis setiap kali ia merender sebuah bitmap, terlepas dari apa yang diserahkan ke renderer. Parameter rotate FPDF_RenderPageBitmap, diekspos di PDFiumPas sebagai nilai TRotation ro0, ro90, ro180 dan ro270 pada TPdf.RenderPage, TPdf.RenderTile dan TPdf.RenderPageThumbnail, tidak mengatur sudut tempat sebuah halaman seharusnya berakhir; parameter rotate mengatur seberapa banyak rotasi tambahan yang dilapiskan di atas apa pun yang sudah dispesifikasikan dictionary halaman, itulah sebabnya setiap satu dari metode itu men-default-kannya ke ro0
TPdf.PageRotation membaca nilai /Rotate yang sama itu lewat FPDFPage_GetRotation, dan kode aplikasi seringkali membutuhkannya untuk alasan yang tidak ada hubungannya dengan rendering, seperti memutuskan bagaimana menata sebuah anotasi dalam ruang halaman. Jebakannya adalah satu baris tunggal: memasukkan PageRotation ke dalam argumen Rotation milik RenderPage, mengharapkan pemanggilan itu menormalisasi halaman menjadi tegak. Sebuah halaman yang sudah disimpan dengan /Rotate 90 ditampilkan dengan benar, terotasi, di viewer mana pun yang sesuai standar, PDFium termasuk; tambahkan ro90 lagi di atasnya dan halaman itu berayun ke 180 derajat alih-alih 90 yang dimaksudkan, sementara sebuah halaman tanpa rotasi sama sekali diputar seperempat putaran yang tidak diinginkan tanpa alasan
// 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, []);
Untuk apa sebenarnya parameter Rotation
Parameter Rotation memiliki tempatnya sendiri dalam API untuk pekerjaan yang benar-benar berbeda: menambahkan sebuah rotasi khusus-tampilan yang tidak ada hubungannya dengan orientasi tersimpan sebuah halaman, jenis yang diterapkan sebuah tombol toolbar rotate-view tanpa menyentuh file yang mendasarinya. TPdfView menjaga kedua konsep itu sebagai dua properti terpisah persis untuk alasan ini. TPdfView.PageRotation mencerminkan /Rotate milik halaman itu sendiri dan, lewat FPDFPage_SetRotation, bisa menulis kembali sebuah nilai baru ke dalam dokumen; TPdfView.Rotation adalah sebuah properti sementara, khusus-tampilan yang defaultnya ro0 dan tidak pernah menyentuh file. Membaca properti pertama dan menulisnya ke properti kedua adalah seluruh bug ini dalam satu kalimat
// 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;
Mengapa ukuran fit-zoom rusak dengan cara yang sama?
Ukuran fit-zoom rusak karena alasan bayangan-cermin: perhitungannya dimulai dari pasangan angka yang salah alih-alih sudut yang salah. Sebuah cara umum mengukur sebuah kotak thumbnail meminta PDFium lebar dan tinggi sebuah halaman, membandingkan aspect ratio itu dengan kotak yang tersedia, dan menghitung rectangle terbesar yang muat di dalamnya — yang bekerja bersih untuk sebuah halaman tak-terotasi. Perhitungan yang sama diam-diam gagal untuk sebuah halaman /Rotate 90 atau /Rotate 270 ketika lebar dan tinggi itu berasal dari sebuah pemanggilan yang melaporkan ukuran intrinsik, tak-terotasi milik halaman: sebuah halaman potret A4 yang membawa /Rotate 90 tetap melaporkan kira-kira 595 kali 842 poin, meski PDFium merendernya, dengan benar, pada kira-kira 842 kali 595 begitu rotasi berlaku, dan sebuah kotak fit yang dihitung dari pasangan tak-terotasi itu berakhir berbentuk untuk orientasi yang sama sekali salah
FPDF_GetPageSizeByIndex adalah satu contoh konkret sebuah pemanggilan yang melaporkan ukuran intrinsik, tak-terotasi itu sesuai desain, yang membuatnya nyaman untuk memindai dimensi halaman tanpa memuat setiap halaman dan berisiko untuk matematika fit-zoom yang lupa memperhitungkannya. Perbaikannya mengikuti langsung dari menamai masalahnya: periksa rotasi halaman sebelum melakukan aritmetika fit, tukar lebar dan tinggi kapan pun rotasi itu 90 atau 270 derajat, hitung kotak fit dari pasangan yang tertukar itu, dan tetap serahkan ro0 ke pemanggilan render sesungguhnya, karena PDFium tetap yang menerapkan rotasi sesungguhnya
Membuat thumbnail benar tanpa menciptakan ulang matematika fit
TPdf.RenderPageThumbnail sudah membawa perbaikan ini, sehingga jalur terpendek ke sebuah thumbnail yang benar adalah memanggilnya alih-alih merakit ulang logika fit-dan-rotate dengan tangan. Diberikan sebuah indeks halaman berbasis-1 dan sebuah lebar dan tinggi maksimum, RenderPageThumbnail menghitung sebuah kotak fit, mengoreksinya untuk sebuah /Rotate sebesar 90 atau 270 secara internal, dan mengembalikan sebuah bitmap milik-pemanggil tanpa mengganggu halaman saat ini dokumen tersebut atau memicu sebuah event OnPageChange — yang penting untuk sebuah strip thumbnail yang dibangun berdampingan dengan sebuah viewer hidup pada instance TPdf yang sama
// 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;
Helper FitBox layak dipertahankan bagaimanapun juga, karena RenderPageThumbnail hanya mencakup kasus bitmap-tunggal. Sebuah grid thumbnail kustom, sebuah strip print-preview, atau sebuah dialog pemilih-halaman yang menata beberapa halaman terhadap kotak independen membutuhkan matematika fit sadar-rotasi yang sama tanpa harus menginginkan sebuah bitmap baru untuk setiap tile, dan mode zoom fit-page dan fit-width milik TPdfView sendiri mengandalkan ide yang identik secara internal, memilih antara lebar dan tinggi sebuah halaman untuk perhitungan zoom-ratio berdasarkan rotasi tampilan saat ini sebelum membandingkannya dengan area klien yang tersedia. Jika performa zoom dan scrolling dalam viewer semacam itu adalah masalah berikutnya dalam daftar, tulisan pendamping tentang render caching dan zoom mulus dalam sebuah viewer Delphi berbasis PDFium melanjutkan tepat di mana ukuran yang benar berhenti
Menemukan sebuah double rotation sebelum seorang pelanggan melakukannya
Sebuah double rotation memiliki satu tanda visual yang andal: sebuah halaman yang dirotasi 90 derajat pada saat masuk keluar terlihat terotasi 180 relatif terhadap sisa dokumen, bukan 90, karena ro90 tambahan itu bertumpuk di atas ro90 milik halaman itu sendiri alih-alih menggantikannya. Sebuah fixture uji yang hanya dibangun dari halaman /Rotate 0 tidak akan pernah menangkap ini, karena menambahkan ro0 ke ro0 tetap ro0 dan bug itu tetap tak terlihat; sebuah fixture membutuhkan setidaknya satu halaman yang disimpan dengan /Rotate 90 dan satu dengan /Rotate 270 sebelum sebuah jalur kode thumbnail atau fit-zoom bisa dipercaya
Pipeline halaman-ke-bitmap dasar yang dibahas di merender halaman PDF ke JPEG dengan PDFium Component sudah merender halaman yang terotasi dengan benar tanpa kode kasus-khusus apa pun, persis karena ia membiarkan Rotation pada default ro0-nya dan membiarkan PDFium menerapkan /Rotate dengan sendirinya. Bug double-rotation hanya muncul begitu kode aplikasi mulai membaca kembali PageRotation dan memasukkannya ke suatu tempat yang bukan miliknya
Pemanggilan render sadar-rotasi dan ukuran thumbnail yang dijelaskan di sini adalah bagian dari PDFium Component untuk Delphi dan C++Builder, berdampingan dengan sisa API rendering, viewing, dan ekstraksi-teks yang dibangun di atas kelas TPdf dan TPdfView yang sama