Tehnički članak

PDFlibPas: TrimBox, BleedBox i CropBox zadane vrijednosti

Kad PDF stranica nema TrimBox, njezin djelotvorni TrimBox jest CropBox stranice, a kad nedostaje i CropBox, onda je to MediaBox. BleedBox i ArtBox slijede isto pravilo. PDFlibPas, PDF Library za Delphi, primjenjuje taj lanac zadanih vrijednosti dosljedno u GetPageBox, HasPageBox i CapturePageEx od v3.539.44, i ignorira produkcijske okvire postavljene na /Pages čvor, jer ISO 32000-1 ne dopušta da se nasljeđuju

Zvuči kao fusnota dok ne impozirate posao. Zamislite unutrašnjost knjige s MediaBoxom od 6,25 × 9,25 inča, CropBoxom postavljenim na 6 × 9 inča reza, i bez TrimBoxa, jer ga tko god ga je izvezao nije pomislio zapisati. Zatražite trim box, a dobijete media box, i svaka ćelija na vašem tiskarskom arku vuče osminu inča ispusta i sluga u susjeda. PDFlibPas je imao defekte točno na tom području, popravljene u v3.539.42 i v3.539.44, a način na koji su popravljeni nešto govori o tome kako bi semantika okvira stranica trebala biti implementirana u svakoj PDF biblioteci

Koji se okvir primjenjuje kad stranica nema TrimBox?

Odgovor je fiksni lanac zadanih vrijednosti iz ISO 32000-1 §14.11.2: CropBox zadano jest MediaBox, a BleedBox, TrimBox i ArtBox svaki zadano jesu CropBox. Ništa osim CropBoxa ne pada izravno na MediaBox. Stranica koja definira samo MediaBox stoga ima pet identičnih okvira, a stranica koja definira MediaBox plus CropBox ima četiri okvira jednaka CropBoxu

OkvirPDFlibPas BoxTypeZadano kad nemaNasljedivo s /Pages
MediaBox1Ništa, unos je obavezanDa
CropBox2MediaBoxDa
BleedBox3CropBoxNe
TrimBox4CropBoxNe
ArtBox5CropBoxNe

Dvostupanjski lanac važan je jer CropBox sam može biti naslijeđen. Djelotvorni TrimBox stranice koja nema ni TrimBox ni vlastiti CropBox jest CropBox najbližeg predka koji ga ima, a ako ni toga, naslijeđeni MediaBox. Spec dodaje još jedno pravilo koje se lako zaboravi: crop, bleed, trim i art okviri ne smiju prelaziti media box, a ako prelaze, djelotvorno se svode na svoj presjek s njim. PDFlibPas javlja svaki okvir kako je spremljen u datoteci, pa bi validator koji rukuje nepouzdanim ulazom trebao stezati prema samom MediaBoxu

PDFlibPas lanac zadanih vrijednosti okvira stranice gdje CropBox zadano jest MediaBox, a BleedBox, TrimBox i ArtBox svaki zadano jesu CropBox, nacrtan uz unutrašnjost knjige s MediaBoxom od 450 puta 666 točaka i CropBoxom od 432 puta 648 točaka koji postaje djelotvorni rez kad TrimBox ne postoji
Ništa osim CropBoxa ne pada izravno na MediaBox, pa stranica sa samo MediaBoxom ima pet identičnih okvira

Koje atribute stranice /Pages čvor može prenijeti dalje?

Točno četiri: Resources, MediaBox, CropBox i Rotate. ISO 32000-1 §7.7.3.4 definira nasljeđivanje atributa, a Tablica 30 označava samo ta četiri unosa objekta stranice kao nasljediva. BleedBox, TrimBox i ArtBox pripadaju listnoj stranici. TrimBox zapisan u /Pages čvor nije naslijeđena vrijednost; to je nestandardni ključ kojeg sukladni čitač ignorira

Nestandardne datoteke poput te postoje, obično s jednim TrimBoxom na korijenskom čvoru stabla stranica kao kraticom za „svaka stranica ima ovaj rez". Kratica izgleda ispravno u svakom alatu koji prevaljuje /Parent za svaki ključ, i to je problem: datoteka sada znači dvije stvari ovisno o tome tko je čita. Čitač koji slijedi spec ne vidi TrimBox i koristi CropBox, dok čitač koji nasljeđuje sve vidi vrijednost roditelja. U prepress pipelineu ta dvosmislenost završi na tiskarskom arku

PDFlibPas nasljeđivanje stabla stranica gdje samo Resources, MediaBox, CropBox i Rotate prelaze Pages čvor, pa je TrimBox parkiran na korijenu nestandardni ključ koji sukladni čitači ignoriraju; prije v3.539.44 dvije neovisne putanje koda nasljeđivale su ga i javljale različite veličine reza za jedan dokument
Datoteka znači dvije stvari ovisno o tome tko je čita, i u prepress pipelineu ta dvosmislenost doskoči na tiskarski ark

PDF/X (ISO 15930) tokovi rada ovise o TrimBoxu za gotovu veličinu, a PDF/X profili zahtijevaju da svaka stranica deklarira TrimBox ili ArtBox. Okvir parkiran na /Pages čvoru ne zadovoljava taj zahtjev, jer ključ nikad ne dođe do objekta stranice. Preflight bi trebao označiti takve datoteke, a ne tiho ih čitati jednim ili drugim načinom

Što je PDFlibPas bio pogriješio prije v3.539.44?

PDFlibPas je imao tri odvojena defekta, sve u rasponu između onoga što spec kaže i onoga što su dvije neovisne putanje koda radile. Prvi je popravljen u v3.539.42, a druga dva u v3.539.44

Produkcijski okviri padali su na MediaBox pri hvatanju

Prije v3.539.42 interna rutina koja priprema stranicu za hvatanje (kopira naslijeđene unose na stranicu i popunjava nedostajuće okvire) davala je BleedBoxu, TrimBoxu i ArtBoxu MediaBox vrijednosti kad su nedostajale. CapturePageEx s opcijama 2 do 4 čita svoj obodni pravokutnik iz točno tih popunjenih unosa, pa se na stranici koja definira samo CropBox zahtjevom za trim box hvatao cijeli media box. GetPageBox je već primjenjivao CropBox zadano, a CapturePageEx referenca je oduvijek govorila da se crop box koristi kad traženi okvir nedostaje; kod hvatanja nije se slagao ni s jednim ni s drugim. Od v3.539.42 tri produkcijska okvira padaju na CropBox stranice, koji u tom trenutku već stoji na stranici (vlastiti, kopiran od predka ili popunjen iz MediaBoxa), a samo se CropBox vraća na MediaBox

Dvije nasljedne putanje, jedno semantičko pravilo

Drugi defekt bilo je samo nestandardno nasljeđivanje, a suptilan dio bio je što je PDFlibPas razrješivao okvire duž dviju neovisnih putanja. Upiti okvira (GetPageBox i HasPageBox) prevaljivali su /Parent lanac kroz jednog pomoćnika, a hvatanje kroz zasebnog lokalnog pomoćnika. Oboje je nasljeđivalo svaki ključ, uključujući produkcijske okvire. Popraviti samo jedan od njih proizvelo bi proturječje unutar jednog dokumenta: sa 180-točaka širokim TrimBoxom na /Pages čvoru i 380-točaka širokim CropBoxom na stranici, GetPageBox bi i dalje javljao širinu reza 180 dok bi CapturePageEx gradio form od 380. U v3.539.44 obje putanje ograničavaju /Parent prevaljanje na četiri nasljediva ključa, produkcijski se okviri čitaju samo s lista, a stran unos roditelja ostaje u datoteci netaknut, ni izbrisan ni prepravljen

PDFlibPas HasPageBox povratni kodovi nula, jedan i dva s izravnim i neizravnim poljima koja se oboje računaju kao naslijeđena od v3.539.44, uz CapturePageEx opcije nula do četiri gdje se BleedBox, TrimBox i ArtBox vraćaju na CropBox umjesto MediaBoxa od v3.539.42
Dvije implementacijske ulazne točke za jedno spec pravilo popravljaju se zajedno i testiraju kao matrica od 18 scenarija, s upitom i hvatanjem koji se poklapaju na svakoj datoteci

HasPageBox promašivao izravna roditeljska polja

HasPageBox vraća 0 kad stranica nema okvir traženog tipa, 1 kad stranica ima vlastiti okvir (spremljen izravno ili kroz neizravnu referencu), i 2 kad je MediaBox ili CropBox naslijeđen od predka. Stari je kod vraćao 2 samo kad je naslijeđena vrijednost bila neizravna referenca, pa je naslijeđeno izravno polje vraćalo 0. Popravak razdvaja dereferenciranje od testa polja, i obje reprezentacije sada vraćaju 2. Od v3.539.44 HasPageBox za BleedBox, TrimBox ili ArtBox može vratiti samo 0 ili 1

Lekcija se generalizira daleko izvan okvira stranica. Kad jedan komad spec semantike ima dvije implementacijske ulazne točke u biblioteci, popravite ih zajedno i testirajte ih kao matricu, a ne s jednom datotekom sretnog puta. PDFlibPas regresijski skup ukršta dvije reprezentacije roditeljskog okvira (izravno i neizravno polje) s tri stanja lista (odsutno, izravno polje, neizravno polje) i tri opcije hvatanja (bleed, trim, art), dajući 18 scenarija, i svako provjerava rezultat upita, uhvaćene granice, legitimno MediaBox i CropBox nasljeđivanje i netaknut unos roditelja

Kako čitati djelotvorni TrimBox u Delphiju?

Pozovite GetPageBox(4, Dimension) na odabranoj stranici. PDFlibPas za vas primjenjuje lanac zadanih vrijednosti, pa je rezultat djelotvorni TrimBox ima li ga stranica ili ne. Kombinirajte ga s HasPageBox kad trebate znati odakle vrijednost dolazi, što izvještaj preflighta 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 = vlastiti, 2 = naslijeđen
    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 trenutačnim postavkama koordinata dokumenta. Primjeri ovdje rade sa zadanim vrijednostima: ishodište 0 (dolje lijevo, u skladu s PDF user spaceom) i točke kao mjerna jedinica, pa je dimenzija Top gornji rub mjeren gore od dna stranice. Nakon SetOrigin(1) dimenzije Top i Bottom mjere se dolje od vrha stranice, a nakon SetMeasurementUnits(1) svaka se vrijednost vraća u milimetrima. Širina i visina ne ovise o ishodištu

Nalaženje produkcijskih okvira nasukanih na /Pages čvorovima

Od v3.539.44 okvirni API više ne vidi TrimBox na /Pages čvoru, što je ispravno, ali preflight alat obično želi takvu datoteku prijaviti, a ne je tiho pročitati na spec način. Čvorovi stabla stranica obični su objekti, pa ih niskorazinski objektni API može naći: prevalite brojeve objekata do GetMaxObjectNumber, pročitajte svaki s GetObjectToString i tražite /Pages rječnik koji nosi ključ produkcijskog okvira. Druga polovica provjere jest test po stranici koji PDF/X-u je stvar, a HasPageBox ga sada rješava onako kako bi to učinio PDF/X validator, jer se 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. Produkcijski okviri na čvorovima stabla stranica: nestandardni i ignorirani
  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 vlastiti 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. Neobavezan popravak: 6 x 9 in rez unutar 6.25 x 9.25 in media boxa
  //    (točke, ishodište dolje lijevo: Left, Top, Width, Height)
  if Missing > 0 then
    Log.Add(Format('TrimBox written on %d pages',
      [Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;

Tekstovno poklapanje pragmatična je provjera, a ne parser. Oslanja se na to da PDFlibPas serijalizira svaki unos rječnika kao ključ, jedan razmak i vrijednost, što vrijedi za objekte pročitane natrag kroz GetObjectToString. Korak popravka zaslužuje odluku, a ne refleks: stran unos roditelja može lako biti ono što je autor mislio, ali potvrdite ga protiv radnog naloga prije nego ga učinite službenim. SetPageBoxRange s praznim rasponom primjenjuje okvir na svaku stranicu i vraća broj ažuriranih stranica. Kad je postojeći okvir stranice neizravno polje, koje druga stranica ili /Pages čvor mogu dijeliti, SetPageBox daje toj stranici novo izravno polje umjesto da prepiše dijeljeni objekt. Postavljanje BleedBoxa, TrimBoxa ili ArtBoxa također podiže otključani dokument na PDF 1.3, verziju koja je uvela te unose

Impozicija stranica na TrimBox s CapturePageEx

CapturePageEx(Page, 3) pretvara stranicu u Form XObject čiji je obodni okvir djelotvorni TrimBox stranice, a DrawCapturedPage postavlja taj form na drugu stranicu u bilo kojoj veličini. Od v3.539.42 opcija 3 na stranici bez TrimBoxa daje vam CropBox, kako referenca opisuje, umjesto MediaBoxa sa svim njegovim slugom

Dva svojstva hvatanja oblikuju kod. Hvatanje je destruktivno: uhvaćena stranica uklanja se iz dokumenta, i dokument nikad ne smije pasti na nula stranica, pa dodajte prvi izlazni ark prije nego što išta uhvatite. Hvatanje radi i samo unutar jednog dokumenta, pa prvo povucite svaki ulaz u jedan dokument; tehnike za slaganje i ispreplitanje PDF izvora u jednom prolazu primjenjuju se izravno

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;

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

    // Dodajte i dimenzionirajte prvi ark; NewPage odabire novu stranicu
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Svako hvatanje uklanja stranicu 1, pa sljedeća izvorna stranica iskoči 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 ark: dvije obrezane stranice po arku, jedno uz drugo
    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 trenutačni ark
      // Zadano ishodište: Top je gornji rub, mjeren od dna
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

Hvatanje temeljeno na rezu izreže sve izvan TrimBoxa, što želite za digitalni probni otisak ili cut-and-stack raspored. Za tiskarski ark koji se reže nakon tiska hvatajte s opcijom 2 da ispust preživi, i razmaknite ćelije za širinu ispusta. Budući da hvatanje uklanja izvorne stranice, bookmarkovi i poveznice koje su pokazivale na njih gube svoje ciljeve, pa impozirajte u zasebnu izlaznu datoteku umjesto da uređujete dokument čiju navigaciju još trebate; zamjena stranica bez lomljenja bookmarkova pokriva tu stranu operacija na stranicama

Kad izvor mora ostati netaknut, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) prima iste vrijednosti opcija od 0 do 4 (prenesite Lib.SelectedDocument za trenutačni dokument), ostavlja izvorno stablo stranica nepromijenjenim, normalizira naslijeđenu rotaciju stranice u form matricu i vraća handle koji DrawCapturedPage prihvaća. CapturePageEx ne poništava /Rotate, pa rotirani ulaz prvo treba taj korak, a izravnavanje rotacije stranice bez lomljenja okvira stranica pokriva što se dogodi sa svakim okvirom kad to napravite. Jedno upozorenje za ulaze koji mogu nositi produkcijske okvire na /Pages čvorovima: putanja uvoza razrješuje svoj okvir kroz vlastito traženje predaka, odvojeno od dviju putanja usklađenih u v3.539.44, pa prvo provjerite HasPageBox(4) na izvornoj stranici i prenesite opciju 1 (CropBox) kad vrati 0. To rezultat veže uz spec, a ne uz to kako je datoteka slučajno zapisana

Brza referenca okvira stranica

  • Djelotvorni CropBox: vlastiti CropBox stranice, inače najbliži naslijeđeni CropBox, inače djelotvorni MediaBox (ISO 32000-1 §14.11.2)
  • Djelotvorni BleedBox, TrimBox i ArtBox: vlastiti unos listne stranice, inače djelotvorni CropBox
  • Samo Resources, MediaBox, CropBox i Rotate nasljeđuju se s /Pages čvorova (§7.7.3.4, Tablica 30); produkcijski okviri na /Pages čvorovima ignoriraju 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 okvira, 1 vlastiti okvir stranice (izravan ili neizravan), 2 naslijeđeni MediaBox ili CropBox (izravan ili neizravan)
  • CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox s povratkom na MediaBox, 2 do 4 BleedBox, TrimBox ili ArtBox s povratkom na CropBox
  • Nadogradite na v3.539.44 ili kasniji za dosljedne zadane vrijednosti i nasljeđivanje kroz upite okvira i hvatanje

Okviri stranica mjesto su gdje se tihe PDF zadane vrijednosti susreću s prepress tolerancijama mjerenim dijelovima milimetra, i biblioteka ili primjenjuje te zadane vrijednosti jednako svugdje ili vam uručuje dva odgovora na jedno pitanje. Potpuni okvirni, capture i Form XObject API dokumentiran je na stranici proizvoda PDFlibPas PDF Library za Delphi