Ketika halaman PDF tak punya TrimBox, TrimBox efektifnya adalah CropBox halaman itu, dan ketika CropBox-nya juga tak ada, ya MediaBox. BleedBox dan ArtBox mengikuti aturan yang sama. PDFlibPas, PDF Library untuk Delphi, menerapkan rantai default ini secara konsisten di GetPageBox, HasPageBox, dan CapturePageEx sejak v3.539.44, dan mengabaikan box produksi yang diletakkan di node /Pages, karena ISO 32000-1 tak membiarkannya mewarisi
Itu terdengar seperti catatan kaki sampai Anda mem-impose sebuah job. Bayangkan interior buku dengan MediaBox 6,25 × 9,25 in, CropBox diset ke trim 6 × 9 in, dan tanpa TrimBox, karena siapa pun yang meng-export-nya tak pernah terpikir menulis satu. Minta trim box, yang datang malah media box, dan setiap sel di press sheet Anda menyeret satu per delapan inci bleed dan slug ke tetangganya. PDFlibPas punya cacat persis di area ini, diperbaiki di v3.539.42 dan v3.539.44, dan cara mereka diperbaiki mengatakan sesuatu tentang bagaimana semantik page box seharusnya diimplementasikan di PDF library mana pun
Box mana yang berlaku ketika halaman tak punya TrimBox?
Jawabannya adalah rantai default tetap dari ISO 32000-1 §14.11.2: CropBox default ke MediaBox, dan BleedBox, TrimBox, serta ArtBox masing-masing default ke CropBox. Tak ada apa pun selain CropBox yang default langsung ke MediaBox. Halaman yang hanya mendefinisikan MediaBox karenanya punya lima box identik, dan halaman yang mendefinisikan MediaBox plus CropBox punya empat box yang sama dengan CropBox
| Box | BoxType PDFlibPas | Default saat absen | Bisa mewarisi dari /Pages |
|---|---|---|---|
| MediaBox | 1 | Tak ada, entrinya wajib | Ya |
| CropBox | 2 | MediaBox | Ya |
| BleedBox | 3 | CropBox | Tidak |
| TrimBox | 4 | CropBox | Tidak |
| ArtBox | 5 | CropBox | Tidak |
Rantai dua langkah itu penting karena CropBox bisa jadi sendiri warisan. TrimBox efektif dari halaman yang tak punya TrimBox maupun CropBox milik sendiri adalah CropBox ancestor terdekat yang punya satu, dan kalau tidak ada, MediaBox warisannya. Spesifikasi menambah satu aturan lagi yang gampang dilupakan: box crop, bleed, trim, dan art sebaiknya tidak menjorok melewati media box, dan kalau menjorok, secara efektif ia direduksi menjadi irisannya dengan media box itu. PDFlibPas melaporkan tiap box sebagaimana tersimpan di file, jadi validator yang menangani input tak terpercaya sebaiknya men-clamp sendiri terhadap MediaBox
Atribut halaman mana yang bisa diwariskan node /Pages?
Persis empat: Resources, MediaBox, CropBox, dan Rotate. ISO 32000-1 §7.7.3.4 mendefinisikan pewarisan atribut, dan Table 30 menandai hanya empat entri page object itu sebagai inheritable. BleedBox, TrimBox, dan ArtBox milik halaman leaf. TrimBox yang ditulis ke node /Pages bukan nilai warisan; ia key non-standard yang diabaikan reader yang conforming
File non-standard seperti itu memang ada, biasanya dengan satu TrimBox di node root page tree sebagai singkatan dari "setiap halaman punya trim ini". Singkatan itu tampak benar di tool mana pun yang menelusuri /Parent untuk setiap key, dan itulah masalahnya: file kini berarti dua hal tergantung siapa yang membacanya. Reader yang mengikuti spesifikasi tak melihat TrimBox dan memakai CropBox, sementara reader yang mewarisi segalanya melihat nilai parent. Di pipeline prepress, ambiguitas itu berakhir di press sheet
Workflow PDF/X (ISO 15930) bergantung pada TrimBox untuk ukuran jadi, dan profil PDF/X mewajibkan tiap halaman mendeklarasikan TrimBox atau ArtBox. Box yang diparkir di node /Pages tak memenuhi syarat itu, karena key-nya tak pernah sampai ke page object. Preflight sebaiknya menandai file semacam itu alih-alih diam-diam membacanya dengan cara mana pun
Apa yang dikerjakan salah PDFlibPas sebelum v3.539.44?
PDFlibPas punya tiga cacat terpisah, semuanya di celah antara apa yang dikatakan spesifikasi dan apa yang dikerjakan dua jalur code independen. Yang pertama diperbaiki di v3.539.42, dua lainnya di v3.539.44
Box produksi default ke MediaBox saat capture
Sebelum v3.539.42, rutin internal yang menyiapkan halaman untuk capture (ia menyalin entri warisan ke halaman dan mengisi box yang hilang) memberi BleedBox, TrimBox, dan ArtBox nilai MediaBox ketika ketiganya absen. CapturePageEx dengan opsi 2 sampai 4 membaca bounding rectangle-nya persis dari entri hasil isi itu, sehingga di halaman yang hanya mendefinisikan CropBox, meminta trim box justru menangkap seluruh media box. GetPageBox sudah menerapkan default CropBox, dan referensi CapturePageEx sejak awal menyatakan crop box dipakai ketika box yang diminta tak ada; code capture-nya menyalahi keduanya. Sejak v3.539.42 ketiga box produksi default ke CropBox halaman, yang pada titik itu sudah ada di halaman (miliknya sendiri, disalin dari ancestor, atau diisi dari MediaBox), dan hanya CropBox itu sendiri yang jatuh kembali ke MediaBox
Dua jalur pewarisan, satu aturan semantik
Cacat kedua adalah pewarisan non-standard itu sendiri, dan bagian subtilesnya adalah PDFlibPas me-resolve box lewat dua jalur independen. Query box (GetPageBox dan HasPageBox) menelusuri rantai /Parent lewat satu helper, dan capture menelusurinya lewat helper lokal terpisah. Keduanya mewarisi setiap key, box produksi termasuk. Memperbaiki hanya satu di antaranya akan menghasilkan kontradiksi di dalam satu dokumen: dengan TrimBox selebar 180 point di node /Pages dan CropBox selebar 380 point di halaman, GetPageBox tetap melaporkan lebar trim 180 sementara CapturePageEx membangun form selebar 380. Di v3.539.44 kedua jalur membatasi penelusuran /Parent ke empat key inheritable, box produksi dibaca dari leaf saja, dan entri parent yang nyasar tetap di file tanpa disentuh, tak dihapus dan tak ditulis ulang
HasPageBox melewatkan array parent direct
HasPageBox mengembalikan 0 ketika halaman tak punya box bertipe yang diminta, 1 ketika halaman punya box miliknya sendiri (tersimpan langsung atau lewat indirect reference), dan 2 ketika MediaBox atau CropBox diwarisi dari ancestor. Code lama mengembalikan 2 hanya ketika nilai warisannya indirect reference, sehingga array direct warisan mengembalikan 0. Perbaikannya memisahkan dereferensi dari test array, dan kedua representasi kini mengembalikan 2. Sejak v3.539.44, HasPageBox untuk BleedBox, TrimBox, atau ArtBox hanya bisa mengembalikan 0 atau 1
Pelajarannya berlaku jauh melampaui page box. Ketika satu bagian semantik spesifikasi punya dua entry point implementasi di sebuah library, perbaiki keduanya bersama dan uji sebagai matriks alih-alih dengan satu file happy-path. Set regresi PDFlibPas menyilangkan dua representasi box parent (array direct dan indirect) dengan tiga state leaf (absen, array direct, array indirect) dan tiga opsi capture (bleed, trim, art), menghasilkan 18 skenario, dan tiap skenario mengecek hasil query, bounds yang tertangkap, pewarisan MediaBox dan CropBox yang sah, serta entri parent yang tak tersentuh
Bagaimana membaca TrimBox efektif di Delphi?
Panggil GetPageBox(4, Dimension) di halaman terpilih. PDFlibPas menerapkan rantai default untuk Anda, jadi hasilnya adalah TrimBox efektif entah halamannya punya satu atau tidak. Pasangkan dengan HasPageBox ketika Anda perlu tahu nilai itu datang dari mana, seperti yang biasanya dilaporkan preflight
uses
System.SysUtils, PDFlibrary;
const
BOX_CROP = 2;
BOX_TRIM = 4;
DIM_LEFT = 0;
DIM_WIDTH = 2;
DIM_HEIGHT = 3;
DIM_BOTTOM = 5;
function DescribeTrim(Lib: TPDFlib; Page: Integer): string;
var
Source: string;
begin
Lib.SelectPage(Page);
if Lib.HasPageBox(BOX_TRIM) = 1 then
Source := 'own TrimBox'
else if Lib.HasPageBox(BOX_CROP) <> 0 then // 1 = milik sendiri, 2 = warisan
Source := 'defaulted to the CropBox'
else
Source := 'defaulted to the MediaBox';
Result := Format('page %d: trim %.2f x %.2f pt at (%.2f, %.2f), %s',
[Page,
Lib.GetPageBox(BOX_TRIM, DIM_WIDTH),
Lib.GetPageBox(BOX_TRIM, DIM_HEIGHT),
Lib.GetPageBox(BOX_TRIM, DIM_LEFT),
Lib.GetPageBox(BOX_TRIM, DIM_BOTTOM),
Source]);
end;
var
Lib: TPDFlib;
Page: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('interior.pdf', '') = 1 then
for Page := 1 to Lib.PageCount do
Writeln(DescribeTrim(Lib, Page));
finally
Lib.Free;
end;
end.
Baik GetPageBox maupun SetPageBox bekerja dalam setting koordinat terkini dokumen. Contoh di sini jalan dengan default: origin 0 (kiri bawah, cocok dengan PDF user space) dan points sebagai unit ukur, jadi dimensi Top adalah tepi atas yang diukur naik dari dasar halaman. Setelah SetOrigin(1), dimensi Top dan Bottom diukur turun dari atas halaman sebagai gantinya, dan setelah SetMeasurementUnits(1) setiap nilai kembali dalam milimeter. Width dan height tak bergantung pada origin
Menemukan box produksi yang tertinggal di node /Pages
Sejak v3.539.44 API box tak lagi melihat TrimBox di node /Pages, dan itu benar, tapi tool preflight biasanya ingin melaporkan file semacam itu alih-alih diam-diam membacanya dengan cara spesifikasi. Node page tree adalah objek biasa, jadi API objek low-level bisa menemukannya: telusuri nomor objek sampai GetMaxObjectNumber, baca tiap objek dengan GetObjectToString, dan cari dictionary /Pages yang membawa key box produksi. Paruh kedua pengecekan adalah test per halaman yang dipedulikan PDF/X, dan HasPageBox kini menjawabnya sebagaimana validator PDF/X akan menjawab, karena TrimBox parent tak lagi dihitung
procedure PreflightTrim(Lib: TPDFlib; Log: TStrings);
const
ProductionKeys: array[0..2] of string = ('/BleedBox', '/TrimBox', '/ArtBox');
var
ObjNum, K, Page, Missing: Integer;
Src: string;
begin
// 1. Box produksi di node page tree: non-standard dan diabaikan
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // nomor bebas tidak mengembalikan teks
Src := string(Lib.GetObjectToString(ObjNum));
if Pos('/Type /Pages', Src) = 0 then
Continue;
for K := Low(ProductionKeys) to High(ProductionKeys) do
if Pos(ProductionKeys[K] + ' ', Src) > 0 then
Log.Add(Format('object %d: %s on a /Pages node is not inheritable',
[ObjNum, ProductionKeys[K]]));
end;
// 2. PDF/X: setiap halaman butuh TrimBox atau ArtBox miliknya sendiri
Missing := 0;
for Page := 1 to Lib.PageCount do
begin
Lib.SelectPage(Page);
if (Lib.HasPageBox(4) = 0) and (Lib.HasPageBox(5) = 0) then
begin
Inc(Missing);
Log.Add(Format('page %d: no TrimBox or ArtBox', [Page]));
end;
end;
// 3. Perbaikan opsional: trim 6 x 9 in di dalam media box 6.25 x 9.25 in
// (points, origin kiri-bawah: Left, Top, Width, Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
Cocokan teks itu pengecekan pragmatis, bukan parser. Ia bergantung pada PDFlibPas yang menserialisasi tiap entri dictionary sebagai key, satu spasi, dan nilai, yang berlaku untuk objek yang dibaca kembali lewat GetObjectToString. Langkah perbaikan pantas dapat keputusan, bukan refleks: nilai parent yang nyasar bisa jadi memang yang dimaksudkan penulisnya, tapi konfirmasilah terhadap job ticket sebelum mengesahkannya. SetPageBoxRange dengan range kosong menerapkan box ke setiap halaman dan mengembalikan jumlah halaman yang diperbarui. Ketika box yang sudah ada milik sebuah halaman adalah array indirect, yang mungkin dibagi dengan halaman lain atau node /Pages, SetPageBox memberi halaman itu array direct baru alih-alih menulis ulang objek bersama. Men-set BleedBox, TrimBox, atau ArtBox juga menaikkan dokumen yang tak terkunci ke PDF 1.3, versi yang memperkenalkan entri-entri itu
Mem-impose halaman ke TrimBox dengan CapturePageEx
CapturePageEx(Page, 3) mengubah sebuah halaman menjadi Form XObject yang bounding box-nya adalah TrimBox efektif halaman itu, dan DrawCapturedPage menempatkan form itu di halaman lain pada ukuran apa pun. Sejak v3.539.42, opsi 3 di halaman tanpa TrimBox memberi Anda CropBox, sesuai deskripsi referensinya, alih-alih MediaBox dengan seluruh slug-nya
Dua sifat capture membentuk codenya. Capture bersifat destruktif: halaman yang tertangkap dihapus dari dokumen, dan dokumen tak boleh pernah jatuh ke nol halaman, jadi tambahkan sheet keluaran pertama sebelum menangkap apa pun. Capture juga hanya bekerja di dalam satu dokumen, jadi tarik semua input ke satu dokumen lebih dulu; teknik di meng-collect dan meng-interleave sumber PDF dalam satu pass berlaku langsung
procedure ImposeTwoUp(const InFile, OutFile: string);
var
Lib: TPDFlib;
Captures: array of Integer;
SourceCount, I: Integer;
TrimW, TrimH: Double;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(InFile, '') <> 1 then
raise Exception.Create('Cannot open ' + InFile);
SourceCount := Lib.PageCount;
// Ukuran trim efektif halaman 1 (layout ini mengasumsikan trim seragam)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// Tambahkan dan atur ukuran sheet pertama; NewPage memilih halaman baru
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// Tiap capture menghapus halaman 1, jadi halaman sumber berikutnya naik
SetLength(Captures, SourceCount);
for I := 0 to SourceCount - 1 do
begin
Captures[I] := Lib.CapturePageEx(1, 3); // 3 = TrimBox
if Captures[I] = 0 then
raise Exception.CreateFmt('Capture of source page %d failed', [I + 1]);
end;
// Hanya sheet yang tersisa: dua halaman ter-trim per sheet, berdampingan
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // ukuran sama dengan sheet saat ini
// Origin default: Top adalah tepi atas, diukur dari bawah
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
Capture berbasis trim memotong semua yang di luar TrimBox, dan itu yang Anda mau untuk proof digital atau layout cut-and-stack. Untuk press sheet yang di-trim setelah dicetak, capture dengan opsi 2 agar bleed-nya selamat, dan beri jarak antar sel selebar bleed. Karena capture menghapus halaman sumber, bookmark dan link yang menunjuk ke sana kehilangan targetnya, jadi impose ke file keluaran terpisah daripada mengedit dokumen yang navigasinya masih Anda butuhkan; mengganti halaman tanpa merusak bookmark membahas sisi operasi halaman itu
Ketika sumbernya harus tetap utuh, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) menerima nilai opsi 0 sampai 4 yang sama (berikan Lib.SelectedDocument untuk dokumen saat ini), membiarkan page tree sumber tak berubah, menormalisasi rotasi halaman warisan ke dalam matrix form, dan mengembalikan handle yang diterima DrawCapturedPage. CapturePageEx tak membatalkan /Rotate, jadi input berotasi butuh langkah itu lebih dulu, dan meratakan rotasi halaman tanpa merusak page box menunjukkan apa yang terjadi pada tiap box ketika Anda melakukannya. Satu kewaspadaan untuk input yang mungkin membawa box produksi di node /Pages: jalur import me-resolve box-nya lewat pencarian ancestor miliknya sendiri, terpisah dari dua jalur yang diselaraskan di v3.539.44, jadi cek HasPageBox(4) di halaman sumber lebih dulu dan berikan opsi 1 (CropBox) ketika hasilnya 0. Itu menjaga hasil terikat pada spesifikasi, bukan pada bagaimana kebetulan filenya ditulis
Referensi cepat page box
- CropBox efektif: CropBox milik halaman sendiri, kalau tidak ada CropBox warisan terdekat, kalau tidak ada MediaBox efektif (ISO 32000-1 §14.11.2)
- BleedBox, TrimBox, dan ArtBox efektif: entri milik halaman leaf sendiri, kalau tidak ada CropBox efektif
- Hanya
Resources,MediaBox,CropBox, danRotateyang mewarisi dari node/Pages(§7.7.3.4, Table 30); box produksi di node/Pagesdiabaikan GetPageBox(BoxType, Dimension): BoxType 1 MediaBox, 2 CropBox, 3 BleedBox, 4 TrimBox, 5 ArtBox; Dimension 0 Left, 1 Top, 2 Width, 3 Height, 4 Right, 5 BottomHasPageBox(BoxType): 0 tak ada box, 1 box milik halaman (direct atau indirect), 2 MediaBox atau CropBox warisan (direct atau indirect)CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox dengan fallback MediaBox, 2 sampai 4 BleedBox, TrimBox, atau ArtBox dengan fallback CropBox- Upgrade ke v3.539.44 atau lebih baru untuk default dan pewarisan yang konsisten di seluruh query box dan capture
Page box adalah tempat default senyap milik PDF bertemu toleransi prepress yang diukur dalam pecahan milimeter, dan sebuah library hanya punya dua pilihan: menerapkan default itu dengan cara yang sama di mana pun, atau menyerahkan dua jawaban untuk satu pertanyaan. API box, capture, dan Form XObject lengkap terdokumentasi di halaman produk PDF Library for Delphi PDFlibPas