HotPDF meratakan rotasi halaman PDF dengan THotPDF.FlattenLoadedPageRotation: method ini membungkus konten setiap halaman terotasi dalam transform cm searah jarum jam, menulis ulang setiap page box yang benar-benar dimiliki halaman, memutar geometri anotasi, matrix appearance, destination eksplisit, dan geometri struktur bertag sebesar sudut yang sama, lalu menyetel /Rotate ke 0. Halaman terlihat identik di viewer, tapi sistem koordinatnya kini tegak. Itu penting begitu sebuah tool di hilir, print RIP, atau kode stamping Anda mengabaikan /Rotate dan menaruh sesuatu di user space mentah
Pemicu yang khas adalah scanner atau aplikasi capture mobile yang menulis halaman landscape sebagai media portrait dengan /Rotate 90. Semua viewer menampilkannya dengan benar, jadi tak ada yang sadar sampai seseorang mencap nomor halaman di "kanan bawah" dan hasilnya miring di sepanjang tepi kiri, atau langkah imposition yang hanya membaca /MediaBox menyusun slot portrait untuk halaman landscape. Flatten terdengar seperti pekerjaan matrix satu baris. Dalam praktik ia menyentuh lima page box, tiga jenis geometri anotasi, target link dokumen, dan structure tree, dan masing-masing punya aturannya sendiri di ISO 32000-1
Arah mana yang diputar /Rotate pada halaman PDF?
/Rotate memutar halaman searah jarum jam untuk tampilan dan pencetakan, dalam kelipatan 90 derajat (ISO 32000-1 §7.7.3.3, Tabel 30). Pada 90 derajat tepi kiri media menjadi bagian atas dan tepi atas menjadi sisi kanan, sehingga dalam device space y-ke-bawah pemetaannya adalah X = (y - Bottom) * Scale dan Y = (x - Left) * Scale. Pada 270 derajat tepi kanan menjadi bagian atas. /Rotate juga salah satu dari hanya empat atribut halaman yang bisa diwarisi, bersama /Resources, /MediaBox, dan /CropBox (§7.7.3.4), jadi dictionary halaman tanpa /Rotate sendiri tetap bisa diputar oleh leluhur /Pages. THotPDF.GetLoadedPageRotation menelusuri rantai /Parent dan menormalkan hasilnya ke 0–359, itulah nilai yang Anda mau, bukan key mentah di halaman
Arahnya gampang salah dengan cara yang lolos dari pengujian, dan build HotPDF sebelumnya persis mengalaminya. Matrix halaman-ke-perangkat lama menukar komponen y untuk 90 dan 270, yang menghasilkan refleksi melintasi diagonal alih-alih rotasi: orientasi matrix-nya terbalik relatif terhadap kasus tanpa rotasi. Kedua sudut tetap "terlihat terotasi", bitmap punya lebar dan tinggi yang tertukar, dan round trip dari halaman ke tampilan lalu kembali mengembalikan titik awal, jadi pengecekan dimensi dan test round-trip semuanya lolos. Satu-satunya pemeriksaan yang bisa diandalkan adalah ke mana penanda sudut mendarat, dibandingkan piksel demi piksel terhadap renderer referensi. Karena model viewer, backend render SIMD, dan mapping highlight saling menyalin matrix yang sama, semuanya diperbaiki bersamaan, dan kode flattening kini memakai konvensi searah jarum jam yang sama dengan renderer
Cara FlattenLoadedPageRotation menulis ulang sebuah halaman
FlattenLoadedPageRotation(PageRange, Info) memproses setiap halaman di PageRange yang rotasi efektifnya 90, 180, atau 270, dan mengembalikan jumlah halaman yang diratakannya. PageRange kosong berarti semua halaman; kalau tidak, string memakai sintaks berbasis satu yang biasa, '1-3,7', dan nomor halaman di luar jangkauan melempar exception alih-alih dilewati. Content stream aslinya tak pernah dienkode ulang. Method ini menambahkan di depan sebuah stream baru berisi q 0 -1 1 0 -Bottom Width+Left cm (untuk 90 derajat) ke /Contents halaman, menambahkan stream berisi Q di belakang, dan akhirnya menulis /Rotate 0 eksplisit ke dictionary halaman supaya nilai warisan di node /Pages tak bisa memutar halaman untuk kedua kalinya
var
Pdf: THotPDF;
Info: THPDFRotationFlattenInfo;
Flattened: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
begin
// '' = semua halaman; halaman 0 derajat ikut discan tapi dibiarkan
Flattened := Pdf.FlattenLoadedPageRotation('', Info);
Writeln(Format('Scanned %d, flattened %d pages', [Info.ScannedPageCount, Info.FlattenedPageCount]));
Writeln(Format('Turned %d annotations, %d destinations, %d tagged geometry entries',
[Info.TransformedAnnotationCount, Info.TransformedDestinationCount,
Info.TransformedStructureGeometryCount]));
if Flattened > 0 then
Pdf.SaveLoadedDocument('scanned-batch-upright.pdf');
end;
finally
Pdf.Free;
end;
end;
Record THPDFRotationFlattenInfo layak dicatat log alih-alih dibuang. ScannedPageCount adalah ukuran rentangnya, FlattenedPageCount sama dengan return value, dan tiga counter Transformed... memberi tahu apakah dokumen punya link, bookmark, atau geometri bertag yang menunjuk halaman-halaman yang diputar. Batch yang setiap filenya melaporkan nol destination tak masalah; file PDF/UA bertag yang melaporkan nol geometri struktur padahal Anda mengharapkan bounding box figure adalah sinyal untuk menginspeksinya secara manual
Page box mana yang ditulis ulang flattening, dan dalam urutan apa?
Flattening hanya menulis ulang box yang sudah dimiliki halaman, dan ia membaca setiap box sebelum menulis salah satunya. Urutan itu penting karena rantai default: GetLoadedPageBox(PageIndex, pbCropBox, ...) mengembalikan /MediaBox ketika halaman tak punya /CropBox, dan /BleedBox, /TrimBox, serta /ArtBox default ke CropBox (§14.11.2). Versi sebelumnya memang membaca, mentransform, dan menulis satu box dalam satu waktu. Ia menulis ulang MediaBox lebih dulu, lalu membaca "CropBox", mendapatkan MediaBox yang sudah diputar, memutarnya untuk kedua kalinya, dan menulis CropBox yang tak pernah dimiliki halaman, yang memotong halaman landscape menjadi persegi. Aturan pewarisan terpecah dengan cara yang sama: MediaBox dan CropBox dicari sepanjang rantai /Parent, sedangkan Bleed, Trim, dan ArtBox hanya dihitung kalau mereka duduk di dictionary halaman itu sendiri, jadi /TrimBox nyasar di node /Pages diperlakukan absen dan tak pernah disalin ke halaman
procedure DumpPageGeometry(Pdf: THotPDF; PageIndex: Integer);
var
L, B, R, T: Single;
begin
Writeln('Effective /Rotate: ', Pdf.GetLoadedPageRotation(PageIndex));
if Pdf.GetLoadedPageBox(PageIndex, pbMediaBox, L, B, R, T) then
Writeln(Format('MediaBox [%g %g %g %g]', [L, B, R, T]));
// True bahkan tanpa key /TrimBox: nilainya mundur ke CropBox, lalu MediaBox
if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
Writeln(Format('TrimBox [%g %g %g %g]', [L, B, R, T]));
// Preset Letter; GetLoadedPageVisibleBox membiarkan output tak tersentuh saat gagal
L := 0; B := 0; R := 612; T := 792;
Pdf.GetLoadedPageVisibleBox(PageIndex, L, B, R, T);
Writeln(Format('Visible [%g %g %g %g]', [L, B, R, T]));
end;
Jalankan helper itu sebelum dan sesudah flattening dan angka-angkanya menjelaskan dirinya sendiri. Untuk halaman 90 derajat dengan MediaBox [0 0 612 792], MediaBox hasil flattening menjadi [0 0 792 612]; setiap box yang ditulis ulang dipetakan lewat putaran searah jarum jam yang sama, relatif terhadap origin MediaBox asli, jadi MediaBox yang baru selalu mulai dari origin dan box lain mempertahankan posisinya di dalamnya. GetLoadedPageVisibleBox mengembalikan apa yang ditampilkan viewer dan dicetak printer, yaitu CropBox yang di-clip ke MediaBox dan dinormalkan agar Left lebih kecil dari Right, dan renderer, export SVG, viewer, serta jalur cetak HotPDF semuanya memakai box yang sama itu. Ketika Anda butuh ukuran halaman yang dilihat manusia, panggil GetLoadedPageVisibleBox alih-alih membaca /MediaBox
Kenapa anotasi rusak kalau hanya /Rect yang diputar?
Anotasi rusak karena appearance stream tak digambar langsung ke /Rect. Di bawah §12.5.5, viewer lebih dulu mentransform /BBox form dengan /Matrix-nya, lalu menskalakan dan mentranslasi bounding box dari hasil itu ke /Rect. Putar hanya /Rect, dan stamp 200 × 40 terjepit ke slot 40 × 200, tak terbaca dan berbaring menyamping. FlattenLoadedPageRotation karena itu mengalikan kanan putaran searah jarum jam halaman ke setiap /Matrix appearance (untuk 90 derajat, [0 -1 1 0 0 0] dalam konvensi vektor baris), di seluruh appearance /N, /R, dan /D beserta setiap state di dalamnya. Satu appearance stream bisa dipakai bersama oleh beberapa anotasi atau state, jadi setiap stream diputar tepat sekali per panggilan. Satu kasus tanpa jawaban bersih adalah stream yang dipakai bersama lintas halaman dengan rotasi berbeda; ia mengikuti halaman pertama yang mencapainya
Dua aturan lagi menjaga form field dan sticky note tetap di tempatnya. Entri /MK /R milik widget (§12.5.6.19) adalah sudut berlawanan arah jarum jam, jadi sudut searah jarum jam milik halaman dikurangkan darinya, modulo 360; lewati itu dan regenerasi appearance berikutnya menggambar teks field ke arah yang salah. Anotasi dengan flag NoRotate (posisi bit 5, nilai 16, §12.5.3) tetap tegak di halaman terotasi dan berporos pada sudut kiri atas /Rect-nya, jadi flattening mempertahankan lebar, tinggi, dan penampilan tegaknya, serta hanya memindahkan sudut itu ke tempat putaran menaruhnya. Di luar anotasi, method ini juga memutar /QuadPoints, /Vertices, /L, dan /InkList, menulis ulang destination eksplisit yang menyebut halaman (titik /XYZ, rectangle /FitR, dan /FitH / /FitV yang bertukar pada 90 dan 270 derajat, §12.3.2.2), dan mentransform geometri bertag seperti entri /BBox atribut untuk elemen struktur yang /Pg-nya halaman itu
Apa yang tak dicakup flattening?
Flattening adalah penulisan ulang geometris atas objek milik satu halaman, dan beberapa situasi jatuh di luarnya secara senyap, bukan gaduh
- Halaman yang rotasi efektifnya sudah 0, atau yang MediaBox-nya hilang atau berlebar atau tinggi nol, dilewati tanpa error; bandingkan return value dengan jumlah halaman yang Anda harapkan berubah
- Form XObject yang direferensikan dari resource halaman mempertahankan
/BBoxmereka sendiri di form space, karenacmterluar sudah memutarnya; scan structure tree hanya mengikuti/Kdan/Asehingga tak pernah masuk ke resource atau anotasi halaman untuk kedua kalinya - Destination ditemukan dengan memindai setiap objek tak langsung sekali per halaman yang diratakan, jadi dokumen besar dengan ratusan halaman terotasi membayar penelusuran itu untuk masing-masing
- Page renderer HotPDF tak menggambar anotasi, jadi pemeriksaan visual stamp yang diputar butuh
FlattenLoadedAnnotationslebih dulu
// Panggang appearance ke konten supaya renderer bisa menampilkannya,
// lalu render halaman 1 sebelum dan sesudah /Rotate-nya dicabut
Pdf.FlattenLoadedAnnotations('1');
Before := Pdf.RenderLoadedPageToBitmap(0, 96);
try
Pdf.FlattenLoadedPageRotation('1', Info);
After := Pdf.RenderLoadedPageToBitmap(0, 96);
try
Assert((Before.Width = After.Width) and (Before.Height = After.Height));
// Bandingkan piksel penanda sudut di sini, bukan sekadar dimensinya
finally
After.Free;
end;
finally
Before.Free;
end;
Untuk latar yang lebih dalam, sisi anotasi dari cerita ini berlanjut di menyintesis appearance anotasi sebelum diratakan, renderer di balik perbandingan sebelum-sesudah dibahas di merender halaman PDF termuat ke bitmap, dan redaksi dan N-up stitching pada PDF termuat memperlihatkan teknik append content stream yang sama yang diandalkan prefiks dan sufiks rotasi. HotPDF, termasuk FlattenLoadedPageRotation dan reader page box, tersedia untuk Delphi dan C++Builder di halaman HotPDF Delphi PDF component