Artikel Teknis

TrimBox, BleedBox, CropBox: Rantai Default di PDFlibPas

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

BoxBoxType PDFlibPasDefault saat absenBisa mewarisi dari /Pages
MediaBox1Tak ada, entrinya wajibYa
CropBox2MediaBoxYa
BleedBox3CropBoxTidak
TrimBox4CropBoxTidak
ArtBox5CropBoxTidak

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

Rantai default page box PDFlibPas di mana CropBox default ke MediaBox dan BleedBox, TrimBox, serta ArtBox masing-masing default ke CropBox, digambar di samping interior buku dengan MediaBox 450 kali 666 point dan CropBox 432 kali 648 point yang menjadi trim efektif ketika tak ada TrimBox
Tak ada selain CropBox yang default langsung ke MediaBox, jadi halaman yang hanya punya MediaBox punya lima box identik

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

Pewarisan page tree PDFlibPas di mana hanya Resources, MediaBox, CropBox, dan Rotate yang mewarisi lewat node Pages, sehingga TrimBox yang diparkir di root adalah key non-standard yang diabaikan reader conforming; sebelum v3.539.44 dua jalur code independen mewarisinya dan melaporkan ukuran trim berbeda untuk satu dokumen
File berarti dua hal tergantung siapa yang membacanya, dan di pipeline prepress ambiguitas itu mendarat 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

Kode kembalian HasPageBox PDFlibPas nol, satu, dan dua dengan array direct maupun indirect sama-sama dihitung sebagai warisan sejak v3.539.44, di samping opsi CapturePageEx nol sampai empat di mana BleedBox, TrimBox, dan ArtBox jatuh kembali ke CropBox alih-alih MediaBox sejak v3.539.42
Dua entry point implementasi untuk satu aturan spesifikasi diperbaiki bersama dan diuji sebagai matriks 18 skenario, dengan query dan capture sepakat di setiap file

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, dan Rotate yang mewarisi dari node /Pages (§7.7.3.4, Table 30); box produksi di node /Pages diabaikan
  • 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 Bottom
  • HasPageBox(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