Odborný článok

PDFlibPas page boxy: predvolené TrimBox, BleedBox a CropBox

Keď PDF strana nemá TrimBox, jej efektívny TrimBox je CropBox strany a keď chýba aj CropBox, je to MediaBox. BleedBox a ArtBox sa držia toho istého pravidla. PDFlibPas, PDF Library for Delphi, aplikuje tento predvolený reťazec konzistentne v GetPageBox, HasPageBox a CapturePageEx od v3.539.44 a ignoruje produkčné boxy položené na uzle /Pages, lebo ISO 32000-1 nedovoľuje, aby sa dedili

Číta to ako poznámku pod čiarou, kým neimposujete zakázku. Predstavte si vnútro knihy s MediaBoxom 6,25 × 9,25 palca, CropBoxom nastaveným na 6 × 9 palcový orez a bez TrimBoxu, lebo ten, kto to exportoval, nikdy nepomyslel na to, že ho zapíše. Poprojte si trim box a dostanete media box a každá bunka na tlačovej forme ťahá osminku palca bleed a slugu do svojho suseda. PDFlibPas mal práve v tejto oblasti defekty, opravené vo v3.539.42 a v3.539.44, a spôsob ich opravy niečo vraví o tom, ako by sa page-box sémantika mala implementovať v akejkoľvek PDF knižnici

Ktorý box platí, keď strana nemá TrimBox?

Odpoveďou je fixný predvolený reťazec z ISO 32000-1 §14.11.2: CropBox sa predvolene berie z MediaBoxu a BleedBox, TrimBox a ArtBox sa každý predvolene berie z CropBoxu. Iba CropBox sa predvolene berie priamo z MediaBoxu. Strana definujúca len MediaBox má preto päť identických boxov a strana definujúca MediaBox plus CropBox má štyri boxy rovné CropBoxu

BoxPDFlibPas BoxTypePredvolený pri neprítomnostiDediteľný z /Pages
MediaBox1Žiadny, položka je povinnáÁno
CropBox2MediaBoxÁno
BleedBox3CropBoxNie
TrimBox4CropBoxNie
ArtBox5CropBoxNie

Dvojkrokový reťazec má význam, lebo aj samotný CropBox môže byť zdedený. Efektívny TrimBox strany bez vlastného TrimBoxu aj CropBoxu je CropBox najbližšieho predka, ktorý nejaký má, a ak nie, zdedený MediaBox. Špecifikácia pridáva ešte jedno pravidlo, ktoré sa ľahko zabúda: crop, bleed, trim aj art boxy nemajú presahovať media box a ak presahujú, fakticky sa zmenšia na ich prienik s ním. PDFlibPas hlási každý box tak, ako je uložený v súbore, takže validátor obsluhujúci nedôveryhodný vstup by sa mal sám zastrapať proti MediaBoxu

PDFlibPas predvolený reťazec page boxov, kde sa CropBox predvolene berie z MediaBoxu a BleedBox, TrimBox a ArtBox sa každý berie z CropBoxu, nakreslené vedľa vnútra knihy s MediaBoxom 450 krát 666 bodov a CropBoxom 432 krát 648 bodov, ktorý sa stáva efektívnym orezom, keď TrimBox neexistuje
Nič okrem CropBoxu sa nepredvoluje priamo z MediaBoxu, takže strana s len MediaBoxom má päť identických boxov

Ktoré atribúty strany môže uzol /Pages odovzdať ďalej?

Presne štyri: Resources, MediaBox, CropBox a Rotate. ISO 32000-1 §7.7.3.4 definuje dedenie atribútov a Table 30 označuje ako dediteľné len tieto štyri položky page objektu. BleedBox, TrimBox a ArtBox patria listovej strane. TrimBox zapísaný do uzlu /Pages nie je zdedená hodnota; je to neštandardný kľúč, ktorý konformný čítač ignoruje

Takéto neštandardné súbory existujú, typicky s jediným TrimBoxom na koreňovom uzle page stromu ako skratka za „každá strana má tento orez“. Skratka vyzerá správne v každom nástroji, ktorý prechádza /Parent pre každý kľúč, a v tom je problém: súbor teraz znamená dve veci podľa toho, kto ho číta. Čítač nasledujúci špecifikáciu nevidí žiadny TrimBox a použije CropBox, zatiaľ čo čítač dediace všetko vidí rodičovskú hodnotu. V prepress pipeline tá dvojsmyselnosť skončí na tlačovej forme

PDFlibPas dedenie v page strome, kde len Resources, MediaBox, CropBox a Rotate prechádzajú cez uzol Pages, takže TrimBox zaparkovaný na koreni je neštandardný kľúč, ktorý konformní čítači ignorujú; pred v3.539.44 ho dve nezávislé kódové cesty dedili a hlásili rôzne veľkosti orezu pre jeden dokument
Súbor znamená dve veci podľa toho, kto ho číta, a v prepress pipeline tá dvojsmyselnosť pristáva na tlačovej forme

Pracovné postupy PDF/X (ISO 15930) závisia od TrimBoxu pri hotovej veľkosti a profily PDF/X vyžadujú, aby každá strana deklarovala TrimBox alebo ArtBox. Box zaparkovaný na uzle /Pages tej požiadavke nevyhovie, lebo kľúč sa nikdy nedostane na page objekt. Preflight má také súbory označiť namiesto ich tichého čítania jedným alebo druhým spôsobom

Čo malo PDFlibPas pred v3.539.44 zle?

PDFlibPas mal tri oddelené defekty, všetky v medzere medzi tým, čo hovorí špecifikácia, a tým, čo robili dve nezávislé kódové cesty. Prvý sa opravil vo v3.539.42, ostatné dva vo v3.539.44

Produkčné boxy počas capture predvolené na MediaBox

Pred v3.539.42 dávala interná rutina pripravujúca stranu na capture (kopíruje zdedené položky na stranu a dopĺňa chýbajúce boxy) BleedBoxu, TrimBoxu a ArtBoxu hodnoty MediaBoxu, keď chýbali. CapturePageEx s options 2 až 4 číta svoj ohraničujúci obdĺžnik presne z týchto doplnených položiek, takže na strane definujúcej len CropBox vzala žiadosť o trim box celý media box. GetPageBox už predvolený reťazec z CropBoxu aplikoval a referencia CapturePageEx vždy vravela, že sa použije crop box, keď požadovaný box chýba; capture kód odporoval obojím. Od v3.539.42 sa tri produkčné boxy predvolene berú z CropBoxu strany, ktorý je v tom momente už na strane (vlastný, skopírovaný od predka alebo doplnený z MediaBoxu) a len samotný CropBox prepadne na MediaBox

Dve dedičné cesty, jedno sémantické pravidlo

Druhým defektom bolo samotné neštandardné dedenie a jemnou časťou bolo, že PDFlibPas riešil boxy po dvoch nezávislých cestách. Dotazy na boxy (GetPageBox a HasPageBox) prechádzali reťazec /Parent cez jedného pomocníka a capture ho prechádzal cez samostatného lokálneho pomocníka. Oba dedili každý kľúč, produkčné boxy vrátane. Opraviť len jeden by vyrobilo rozpor vnútri jediného dokumentu: s TrimBoxom širokým 180 bodov na uzle /Pages a CropBoxom širokým 380 bodov na strane by GetPageBox stále hlásil šírku orezu 180, kým CapturePageEx postavil formu širokú 380. Vo v3.539.44 obidve cesty obmedzujú prechod /Parent na štyri dediteľné kľúče, produkčné boxy sa čítajú len z listu a zatúlaná rodičovská položka ostáva v súbore nedotknutá, ani vymazaná, ani prepísaná

PDFlibPas návratové kódy HasPageBox nula, jedna a dva s priamymi aj nepriamymi poľami počítanými ako zdedenými od v3.539.44, vedľa options CapturePageEx nula až štyri, kde BleedBox, TrimBox a ArtBox prepadajú na CropBox namiesto MediaBoxu od v3.539.42
Dva implementačné vstupné body jedného pravidla špecifikácie sa opravia spolu a testujú ako matica 18 scenárov, s dotazom a capture sa zhodujúcimi na každom súbore

HasPageBox prehliadal priame rodičovské polia

HasPageBox vracia 0, keď strana nemá box požadovaného typu, 1, keď má vlastný box (uložený priamo alebo cez nepriamu referenciu) a 2, keď je MediaBox alebo CropBox zdedený od predka. Starý kód vracal 2 len vtedy, keď bola zdedená hodnota nepriamou referenciou, takže zdedené priame pole vrátilo 0. Oprava oddeluje dereferencovanie od testu poľa a obidve reprezentácie teraz vracajú 2. Od v3.539.44 môže HasPageBox pre BleedBox, TrimBox alebo ArtBox vrátiť len 0 alebo 1

Lekcia sa zobecňuje ďaleko za page boxy. Keď má jedna kus sémantiky špecifikácie v knižnici dva implementačné vstupné body, opravte ich spolu a testujte ich ako maticu, nie jedným happy-path súborom. Regresná sada PDFlibPas kríži dve reprezentácie rodičovského boxu (priame a nepriame pole) s tromi stavmi listu (chýbajúci, priame pole, nepriame pole) a tromi capture options (bleed, trim, art), čo dáva 18 scenárov a každý kontroluje výsledok dotazu, zachytené hranice, legitímne dedenie MediaBoxu a CropBoxu aj nedotknutú rodičovskú položku

Ako si v Delphi prečítam efektívny TrimBox?

Zavolajte GetPageBox(4, Dimension) na vybranej strane. PDFlibPas vám aplikuje predvolený reťazec, takže výsledok je efektívny TrimBox bez ohľadu na to, či ho strana má. Spárujte to s HasPageBox, keď potrebujete vedieť, odkiaľ hodnota pochádza, čo bežne chce preflight report

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 = vlastný, 2 = zdedený
    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.

GetPageBox aj SetPageBox pracujú v aktuálnych súradnicových nastaveniach dokumentu. Príklady tu bežia s predvolenými: pôvod 0 (ľavý dolný roh, sediaci s PDF user space) a body ako meracia jednotka, takže dimenzia Top je horná hrana meraná nahor od spodku strany. Po SetOrigin(1) sa dimenzie Top a Bottom merajú nadol od hornej hrany strany a po SetMeasurementUnits(1) prichádza každá hodnota v milimetroch. Šírka a výška od pôvodu nezávisia

Hľadanie produkčných boxov zabehnutých na uzly /Pages

Od v3.539.44 box API už nevidí TrimBox na uzle /Pages, čo je správne, ale preflight nástroj zvyčajne chce takýto súbor nahlásiť, nie ho potichu čítať po špecifikačne. Uzly page stromu sú obyčajné objekty, takže ich dokáže nájsť nízkoúrovňové objektové API: prechádzajte čísla objektov po GetMaxObjectNumber, čítajte každý cez GetObjectToString a hľadajte slovník /Pages nesúci kľúč produkčného boxu. Druhou polovicou kontroly je test na stranu, o ktorý sa stará PDF/X a HasPageBox na ňu teraz odpovedá tak, ako by odpovedal PDF/X validátor, lebo rodičovský TrimBox sa už nepočíta

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. Produkčné boxy na uzloch page stromu: neštandardné a ignorované
  for ObjNum := 1 to Lib.GetMaxObjectNumber do
  begin
    Src := '';                                // voľné čísla nevracajú žiadny text
    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: každá strana potrebuje vlastný TrimBox alebo 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. Voliteľná oprava: orez 6 x 9 palcov vo vnútri media boxu 6,25 x 9,25 palca
  //    (body, pôvod v ľavom dolnom rohu: Left, Top, Width, Height)
  if Missing > 0 then
    Log.Add(Format('TrimBox written on %d pages',
      [Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;

Textová zhoda je pragmatická kontrola, nie parser. Spolieha sa na to, že PDFlibPas serializuje každú položku slovníka ako kľúč, jednu medzeru a hodnotu, čo drží pre objekty prečítané cez GetObjectToString. Opravný krok si zaslúži rozhodnutie, nie reflex: zatúlaná rodičovská hodnota môže pokojne byť to, čo autor zamýšľal, ale overte to proti job ticketu skôr, než z toho urobíte oficiálnu vec. SetPageBoxRange s prázdnym rozsahom aplikuje box na každú stranu a vráti počet aktualizovaných strán. Keď je existujúci box strany nepriamym poľom, ktoré môže zdieľať ďalšia strana alebo uzol /Pages, SetPageBox dá tej strane nové priame pole namiesto prepisu zdieľaného objektu. Nastavenie BleedBoxu, TrimBoxu alebo ArtBoxu tiež pozdvihne odomknutý dokument na PDF 1.3, verziu, ktorá tieto položky zaviedla

Imposovanie strán na TrimBox cez CapturePageEx

CapturePageEx(Page, 3) zmení stranu na Form XObject, ktorého ohraničujúci box je efektívny TrimBox strany, a DrawCapturedPage umiestni tú formu na inú stranu v ľubovoľnej veľkosti. Od v3.539.42 dá option 3 na strane bez TrimBoxu CropBox, ako popisuje referencia, namiesto MediaBoxu so všetkým slugom

Dve vlastnosti capture tvarujú kód. Capture je deštruktívny: zachytená strana sa odstráni z dokumentu a dokument nikdy nemôže klesnúť na nula strán, takže pripojte prvý výstupný hárok skôr, než čokoľvek zachytíte. Capture navyše funguje len vnútri jedného dokumentu, takže najprv stiahnite každý vstup do jediného dokumentu; techniky z článku o kollátovaní a prelúdaní PDF zdrojov v jednom prechode sa aplikujú priamo

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;

    // Efektívna veľkosť orezu strany 1 (toto rozloženie predpokladá uniformný orez)
    Lib.SelectPage(1);
    TrimW := Lib.GetPageBox(4, 2);
    TrimH := Lib.GetPageBox(4, 3);

    // Pripojte a nastavte veľkosť prvého hárka; NewPage vyberie novú stranu
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Každý capture odstráni stranu 1, takže ďalšia zdrojová strana sa posunie nahor
    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;

    // Ostáva len hárok: dva orezané strany na hárok, vedľa seba
    Lib.SelectPage(1);
    for I := 0 to SourceCount - 1 do
    begin
      if (I > 0) and (I mod 2 = 0) then
        Lib.NewPage;                            // rovnakej veľkosti ako aktuálny hárok
      // Predvolený pôvod: Top je horná hrana, meraná od spodku
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

Capture založený na oreze ostripuje všetko mimo TrimBoxu, čo chcete pre digitálny náhľad alebo cut-and-stack rozloženie. Pre tlačovú formu orezávanú po tlači zachytávajte s option 2, aby bleed prežil, a rozmiestnite bunky so vzdialenosťou podľa šírky bleedu. Keďže capture odstraňuje zdrojové strany, bookmarky a linky ukazujúce na ne prídu o svoje ciele, takže imposujte do samostatného výstupného súboru namiesto editovania dokumentu, ktorého navigáciu ešte potrebujete; nahradzovanie strán bez rozbitia bookmarkov pokrýva tú stránku operácií na stranách

Keď zdroj musí ostať nedotknutý, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) berie rovnaké hodnoty options 0 až 4 (podajte Lib.SelectedDocument pre aktuálny dokument), necháva page strom zdroja nezmenený, normalizuje zdedenú rotáciu strany do matice formy a vracia handle, ktorý DrawCapturedPage prijíma. CapturePageEx nerozviaže /Rotate, takže otočený vstup potrebuje ten krok najprv a zrovnanie rotácie strany bez rozbitia page boxov ukazuje, čo sa stane každému boxu, keď to urobíte. Jedna výhrada pre vstupy, ktoré môžu niesť produkčné boxy na uzloch /Pages: importná cesta rieši svoj box cez vlastné vyhľadávanie predkov, oddelené od dvoch ciest zosúladených vo v3.539.44, takže najprv skontrolujte HasPageBox(4) na zdrojovej strane a podajte option 1 (CropBox), keď vráti 0. Tým zostane výsledok viazaný na špecifikáciu, nie na to, ako bol súbor náhodou napísaný

Rýchla referencia page boxov

  • Efektívny CropBox: vlastný CropBox strany, inak najbližší zdedený CropBox, inak efektívny MediaBox (ISO 32000-1 §14.11.2)
  • Efektívny BleedBox, TrimBox a ArtBox: vlastná položka listovej strany, inak efektívny CropBox
  • Z uzlov /Pages dedia len Resources, MediaBox, CropBox a Rotate (§7.7.3.4, Table 30); produkčné boxy na uzloch /Pages sa ignorujú
  • 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 žiadny box, 1 vlastný box strany (priamy alebo nepriamy), 2 zdedený MediaBox alebo CropBox (priamy alebo nepriamy)
  • CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox s prepadom na MediaBox, 2 až 4 BleedBox, TrimBox alebo ArtBox s prepadom na CropBox
  • Upgradujte na v3.539.44 alebo novšiu pre konzistentné predvolené a dedenie cez box dotazy aj capture

Page boxy sú miesto, kde sa tiché predvolené PDF stretávajú s prepress toleranciami meranými v zlomkoch milimetra a knižnica buď aplikuje tie predvolené všade rovnako, alebo vám podá dve odpovede na jednu otázku. Plné box, capture a Form XObject API je zdokumentované na produktovej stránke PDFlibPas PDF Library for Delphi