Tehnički članak

PDFlibPas kutije stranica: TrimBox i CropBox podrazumevano

Kad PDF stranica nema TrimBox, njen efektivni TrimBox je CropBox stranice, a kad i CropBox nedostaje, on je MediaBox. BleedBox i ArtBox prate isto pravilo. PDFlibPas, PDF Library za Delphi, primenjuje ovaj lanac podrazumevanih vrednosti dosledno u GetPageBox, HasPageBox i CapturePageEx od v3.539.44, i ignoriše produkcijske kutije postavljene na /Pages čvor, jer ISO 32000-1 ne dopušta da nasleđuju

Zvuči kao fusnota dok ne isplombirate posao. Zamislite unutrašnjost knjige sa MediaBox-om od 6.25 × 9.25 in, CropBox postavljenim na trim od 6 × 9 in, i bez TrimBox-a, jer ga onaj ko ga je izvezao nije pomislio da upiše. Zatražite trim kutiju, dobijete media kutiju, i svaka ćelija na vašem tiskarskom listu odvlači osminu inča bleed i slug-a ka susedi. PDFlibPas je imao defekte baš na ovom području, popravljene u v3.539.42 i v3.539.44, i način na koji su popravljeni govori nešto o tome kako treba implementirati semantiku kutija stranica u bilo kojoj PDF biblioteci

Koja kutija važi kad stranica nema TrimBox?

Odgovor je fiksni lanac podrazumevanih vrednosti iz ISO 32000-1 §14.11.2: CropBox se podrazumeva na MediaBox, a BleedBox, TrimBox i ArtBox svaki se podrazumeva na CropBox. Ništa osim CropBox-a se ne podrazumeva pravo na MediaBox. Stranica koja definiše samo MediaBox zato ima pet identičnih kutija, a stranica koja definiše MediaBox plus CropBox ima četiri kutije jednake CropBox-u

KutijaPDFlibPas BoxTypePodrazumevana kad odsustvujeNaslediva sa /Pages
MediaBox1Ništa, unos je obavezanDa
CropBox2MediaBoxDa
BleedBox3CropBoxNe
TrimBox4CropBoxNe
ArtBox5CropBoxNe

Dvostepeni lanac je bitan jer CropBox može i sam biti nasleđen. Efektivni TrimBox stranice koja nema ni svoj TrimBox ni svoj CropBox je CropBox najbližeg pretka koji ga ima, a kad ni toga nema, nasleđeni MediaBox. Specifikacija dodaje još jedno pravilo koje se lako zaboravi: crop, bleed, trim i art kutije ne bi trebalo da prelaze media kutiju, i ako prelaze, efektivno se smanjuju na svoj presek sa njom. PDFlibPas prijavljuje svaku kutiju kako je pohranjena u fajlu, pa validator koji radi sa nepouzdanim unosom treba sam da steže prema MediaBox-u

PDFlibPas lanac podrazumevanih vrednosti kutija stranice gde se CropBox podrazumeva na MediaBox a BleedBox, TrimBox i ArtBox svaki na CropBox, nacrtan pored unutrašnjosti knjige sa MediaBox-om od 450 sa 666 tačaka i CropBox-om od 432 sa 648 tačaka koji postaje efektivni trim kad TrimBox ne postoji
Ništa osim CropBox-a se ne podrazumeva pravo na MediaBox, pa stranica sa samo MediaBox-om ima pet identičnih kutija

Koje atribute stranica /Pages čvor može da prenese?

Tačno četiri: Resources, MediaBox, CropBox i Rotate. ISO 32000-1 §7.7.3.4 definiše nasleđivanje atributa, i Table 30 označava samo te četiri unosa objekta stranice kao nasledive. BleedBox, TrimBox i ArtBox pripadaju listu stranice. TrimBox upisan u /Pages čvor nije nasleđena vrednost; to je nestandardni ključ koji korektan čitač ignoriše

Takvi nestandardni fajlovi postoje, tipično sa jednim TrimBox-om na korenskom čvoru stabla stranica kao kraticom za „svaka stranica ima ovaj trim“. Kratnica izgleda ispravno u svakom alatu koji obilazi /Parent za svaki ključ, i to je problem: fajl sada znači dve stvari u zavisnosti ko ga čita. Čitač koji prati specifikaciju ne vidi TrimBox i koristi CropBox, dok čitač koji nasleđuje sve vidi vrednost roditelja. U prepress cevovodu ta dvosmislenost završava na tiskarskom listu

PDFlibPas nasleđivanje stabla stranica gde samo Resources, MediaBox, CropBox i Rotate silaze niz Pages čvor, pa je TrimBox parkiran na korenu nestandardni ključ koji korektni čitači ignorišu; pre v3.539.44 dve nezavisne kodne putanje nasleđivale su ga i prijavljivale različite trim veličine za jedan dokument
Fajl znači dve stvari u zavisnosti ko ga čita, i u prepress cevovodu ta dvosmislenost doskoči na tiskarski list

PDF/X (ISO 15930) tokovi posla zavise od TrimBox-a za gotovu veličinu, i PDF/X profili zahtevaju da svaka stranica deklariše TrimBox ili ArtBox. Kutija parkirana na /Pages čvoru ne ispunjava taj zahtev, jer ključ nikada ne stigne do objekta stranice. Preflight bi trebalo da označi takve fajlove umesto da ih tiho pročita jednim ili drugim načinom

Šta je PDFlibPas radio pogrešno pre v3.539.44?

PDFlibPas je imao tri odvojena defekta, sve u prostoru između onoga što specifikacija kaže i onoga što su dve nezavisne kodne putanje radile. Prvi je popravljen u v3.539.42, druga dva u v3.539.44

Produkcijske kutije podrazumevane na MediaBox pri hvatanju

Pre v3.539.42, interna rutina koja priprema stranicu za hvatanje (kopira nasleđene unose na stranicu i popunjava nedostajuće kutije) davala je BleedBox, TrimBox i ArtBox vrednosti MediaBox-a kad su odsustvovali. CapturePageEx sa opcijama 2 do 4 čita svoj ogradni pravougaonik baš iz tih popunjenih unosa, pa na stranici koja definiše samo CropBox, zahtev za trim kutijom hvatao je celu media kutiju. GetPageBox već je primenjivao CropBox podrazumevanu vrednost, i CapturePageEx referenca je oduvek govorila da se crop kutija koristi kad tražena kutija nedostaje; kod hvatanja se s obema protivrečio. Od v3.539.42 tri produkcijske kutije se podrazumevaju na CropBox stranice, koji je u tom trenutku već na stranici (sopstveni, kopiran od pretka, ili popunjen iz MediaBox-a), i samo CropBox sam vraća se na MediaBox

Dve putanje nasleđivanja, jedno semantičko pravilo

Drugi defekt bilo je nestandardno nasleđivanje samo, i suptilan deo bio je što je PDFlibPas razrešavao kutije duž dve nezavisne putanje. Upiti kutija (GetPageBox i HasPageBox) hodali su /Parent lanac kroz jednog pomoćnika, a hvatanje kroz posebnog lokalnog pomoćnika. Oba su nasleđivala svaki ključ, uključujući produkcijske kutije. Popraviti samo jedan od njih proizvelo bi protivrečnost unutar jednog dokumenta: sa TrimBox-om širokim 180 tačaka na /Pages čvoru i CropBox-om širokim 380 tačaka na stranici, GetPageBox bi i dalje prijavljivao trim širinu 180 dok bi CapturePageEx gradio formu široku 380. U v3.539.44 obe putanje ograničavaju /Parent šetnju na četiri naslediva ključa, produkcijske kutije čitaju se samo sa lista, i zalutali roditeljski unos ostaje u fajlu netaknut, ni obrisani ni prepisani

PDFlibPas HasPageBox povratni kodovi nula, jedan i dva sa direktnim i indirektnim nizovima koji oba računaju kao nasleđeno od v3.539.44, pored CapturePageEx opcija nula do četiri gde se BleedBox, TrimBox i ArtBox vraćaju na CropBox umesto MediaBox od v3.539.42
Dva implementaciona ulaza za jedno pravilo specifikacije popravljaju se zajedno i testiraju kao matrica od 18 scenarija, sa upitom i hvatanjem koji se slažu na svakom fajlu

HasPageBox promašio direktne roditeljske nizove

HasPageBox vraća 0 kad stranica nema kutiju traženog tipa, 1 kad stranica ima sopstvenu kutiju (pohranjenu direktno ili kroz indirektnu referencu), i 2 kad je MediaBox ili CropBox nasleđen od pretka. Stari kod vraćao je 2 samo kad je nasleđena vrednost bila indirektna referenca, pa je nasleđen direktan niz vraćao 0. Popravka odvaja dereferenciranje od testa niza, i obe reprezentacije sada vraćaju 2. Od v3.539.44, HasPageBox za BleedBox, TrimBox ili ArtBox može vratiti samo 0 ili 1

Lekcija se uopštava daleko van kutija stranica. Kad jedan deo semantike specifikacije ima dva implementaciona ulaza u biblioteci, popravite ih zajedno i testirajte ih kao matricu, a ne sa jednim happy-path fajlom. PDFlibPas regresioni skup ukršta dve reprezentacije roditeljske kutije (direktan i indirektan niz) sa tri stanja lista (odsutan, direktan niz, indirektan niz) i tri opcije hvatanja (bleed, trim, art), dajući 18 scenarija, i svaki proverava rezultat upita, uhvaćene granice, legitimno nasleđivanje MediaBox-a i CropBox-a i netaknuti roditeljski unos

Kako pročitati efektivni TrimBox u Delphi-ju?

Zovite GetPageBox(4, Dimension) na izabranoj stranici. PDFlibPas primenjuje lanac podrazumevanih vrednosti za vas, pa je rezultat efektivni TrimBox bilo da ga stranica ima ili ne. Uparete ga sa HasPageBox kad treba da znate odakle vrednost dolazi, što preflight izveštaj obično hoće

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 = own, 2 = inherited
    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.

I GetPageBox i SetPageBox rade u trenutnim koordinatnim podešavanjima dokumenta. Primeri ovde idu sa podrazumevanima: poreklo 0 (dole levo, u skladu sa PDF user space) i tačke kao merna jedinica, pa je Top dimenzija gornja ivica merena naviše od dna stranice. Posle SetOrigin(1) dimenzije Top i Bottom mere se naniže od vrha stranice, a posle SetMeasurementUnits(1) svaka vrednost stiže u milimetrima. Širina i visina ne zavise od porekla

Nalaženje produkcijskih kutija nasukanih na /Pages čvorovima

Od v3.539.44 kutija API više ne vidi TrimBox na /Pages čvoru, što je ispravno, ali preflight alat obično želi takav fajl da prijavi, a ne da ga tiho pročita na spec način. Čvorovi stabla stranica su obični objekti, pa niskonivojni objektni API može da ih nađe: hodajte brojeve objekata do GetMaxObjectNumber, čitajte svaki sa GetObjectToString, i tražite /Pages rečnik koji nosi ključ produkcijske kutije. Druga polovina provere je test po stranici koji PDF/X pazi, i HasPageBox sada na njega odgovara onako kako bi PDF/X validator, jer roditeljski TrimBox više ne računa

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. Produkcijske kutije na čvorovima stabla stranica: nestandardne i ignorisane
  for ObjNum := 1 to Lib.GetMaxObjectNumber do
  begin
    Src := '';                                // slobodni brojevi ne vraćaju tekst
    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: svaka stranica treba sopstveni TrimBox ili ArtBox
  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. Opciona popravka: trim 6 x 9 in unutar media kutije 6.25 x 9.25 in
  //    (tačke, poreklo dole-levo: Left, Top, Width, Height)
  if Missing > 0 then
    Log.Add(Format('TrimBox written on %d pages',
      [Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;

Tekstualno poklapanje je pragmatična provera, ne parser. Oslanja se na to da PDFlibPas serijalizuje svaki unos rečnika kao ključ, jedan razmak i vrednost, što važi za objekte pročitane nazad kroz GetObjectToString. Korak popravke zaslužuje odluku, a ne refleks: zalutala roditeljska vrednost može sasvim biti ono što je autor nameravao, ali potvrdite je protiv radnog naloga pre nego što je učinite zvaničnom. SetPageBoxRange sa praznim opsegom primenjuje kutiju na svaku stranicu i vraća broj ažuriranih stranica. Kad je postojeća kutija stranice indirektan niz, koji druga stranica ili /Pages čvor mogu deliti, SetPageBox daje toj stranici novi direktan niz umesto da prepisuje deljeni objekat. Postavljanje BleedBox, TrimBox ili ArtBox takođe podiže otključan dokument na PDF 1.3, verziju koja je uvela te unose

Plombiranje stranica na TrimBox sa CapturePageEx

CapturePageEx(Page, 3) pretvara stranicu u Form XObject čiji je ogradni okvir efektivni TrimBox stranice, a DrawCapturedPage postavlja tu formu na drugu stranicu u bilo kojoj veličini. Od v3.539.42, opcija 3 na stranici bez TrimBox-a daje vam CropBox, kako referenca opisuje, umesto MediaBox-a sa svim njegovim slug-om

Dve osobine hvatanja oblikuju kod. Hvatanje je destruktivno: uhvaćena stranica se uklanja iz dokumenta, i dokument nikada ne može pasti na nulu stranica, pa dodajte prvi izlazni list pre nego što bilo šta uhvatite. Hvatanje takođe radi samo unutar jednog dokumenta, pa prvo povucite svaki ulaz u jedan dokument; tehnike za slaganje i ispreplitanje PDF izvora u jednom prolazu primenjuju se direktno

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;

    // Efektivna trim veličina stranice 1 (ovaj raspored pretpostavlja ujednačen trim)
    Lib.SelectPage(1);
    TrimW := Lib.GetPageBox(4, 2);
    TrimH := Lib.GetPageBox(4, 3);

    // Dodajte i dimenzionišite prvi list; NewPage bira novu stranicu
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Svako hvatanje uklanja stranicu 1, pa se sledeća izvorna stranica pomera gore
    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;

    // Ostao je samo list: dve odsečene stranice po listu, jedne pored drugih
    Lib.SelectPage(1);
    for I := 0 to SourceCount - 1 do
    begin
      if (I > 0) and (I mod 2 = 0) then
        Lib.NewPage;                            // iste veličine kao trenutni list
      // Podrazumevano poreklo: Top je gornja ivica, mereno od dna
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

Hvatanje zasnovano na trimu iseče sve van TrimBox-a, što hoćete za digitalni probni otisak ili cut-and-stack raspored. Za tiskarski list koji se seče posle štampe, hvatajte opcijom 2 da bleed preživi, i razmaknite ćelije po širini bleed-a. Pošto hvatanje uklanja izvorne stranice, bookmark-i i linkovi koji su pokazivali na njih gube mete, pa plombirajte u odvojeni izlazni fajl umesto da uređujete dokument čiju navigaciju još trebate; zamena stranica bez lomljenja bookmark-a pokriva tu stranu operacije nad stranicama

Kad izvor mora ostati netaknut, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) prima iste opcije od 0 do 4 (prosledite Lib.SelectedDocument za trenutni dokument), ostavlja izvorno stablo stranica nepromenjeno, normalizuje nasleđenu rotaciju stranice u matricu forme, i vraća handle koji DrawCapturedPage prima. CapturePageEx ne poništava /Rotate, pa rotirani ulaz prvo treba taj korak, a izravnavanje rotacije stranice bez lomljenja kutija stranica pokazuje šta se dešava sa svakom kutijom kad to uradite. Jedno upozorenje za ulaze koji mogu nositi produkcijske kutije na /Pages čvorovima: putanja uvoza razrešava svoju kutiju kroz sopstveno potraživanje pretka, odvojeno od dve putanje usklađene u v3.539.44, pa prvo proverite HasPageBox(4) na izvornoj stranici i prosledite opciju 1 (CropBox) kad vrati 0. To drži rezultat vezanim za specifikaciju, a ne za to kako je fajl slučajno upisan

Brzi pregled kutija stranice

  • Efektivni CropBox: sopstveni CropBox stranice, inače najbliži nasleđeni CropBox, inače efektivni MediaBox (ISO 32000-1 §14.11.2)
  • Efektivni BleedBox, TrimBox i ArtBox: sopstveni unos lista stranice, inače efektivni CropBox
  • Samo Resources, MediaBox, CropBox i Rotate nasleđuju se sa /Pages čvorova (§7.7.3.4, Table 30); produkcijske kutije na /Pages čvorovima ignorišu se
  • 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 nema kutije, 1 sopstvena kutija stranice (direktna ili indirektna), 2 nasleđeni MediaBox ili CropBox (direktan ili indirektan)
  • CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox sa vraćanjem na MediaBox, 2 do 4 BleedBox, TrimBox ili ArtBox sa vraćanjem na CropBox
  • Nadogradite na v3.539.44 ili noviji za dosledne podrazumevane vrednosti i nasleđivanje kroz upite kutija i hvatanje

Kutije stranice su mesto gde tihe podrazumevane vrednosti PDF-a sreću prepress tolerancije merene delovima milimetra, i biblioteka ili primenjuje te vrednosti isto svuda ili vam uručuje dva odgovora na jedno pitanje. Kompletan API kutija, hvatanja i Form XObject dokumentovan je na stranici proizvoda PDFlibPas PDF Library for Delphi