Teknisk artikel

PDFlibPas sideboxes: TrimBox-, BleedBox- og CropBox-defaults

Har en PDF-side ingen TrimBox, er dens effektive TrimBox sidens CropBox, og mangler CropBox'en også, er det MediaBox. BleedBox og ArtBox følger samme regel. PDFlibPas, PDF Library til Delphi, anvender denne default-kæde konsekvent i GetPageBox, HasPageBox og CapturePageEx siden v3.539.44, og den ignorerer production boxes, der er placeret på en /Pages-node, for ISO 32000-1 tillader dem ikke at nedarve

Det lyder som en fodnote, indtil du imposer et job. Forestil dig en bogindside med et MediaBox på 6,25 × 9,25 tommer, en CropBox sat til 6 × 9 tommer-trimmen og ingen TrimBox, for den, der eksporterede den, tænkte aldrig på at skrive én. Spørg efter trim-boxen, få media-boxen i stedet, og hver celle på dit trykark slæber en ottendedel tommer bleed og slug ind i sin nabo. PDFlibPas havde defekter i netop dette område, rettet i v3.539.42 og v3.539.44, og måden de blev rettet på, siger noget om, hvordan side-box-semantik bør implementeres i ethvert PDF-bibliotek

Hvilken box gælder, når en side ingen TrimBox har?

Svaret er en fast default-kæde fra ISO 32000-1 §14.11.2: CropBox har MediaBox som default, og BleedBox, TrimBox og ArtBox har hver CropBox som default. Ingenting undtagen CropBox har MediaBox som direkte default. En side, der kun definerer et MediaBox, har derfor fem identiske boxes, og en side, der definerer et MediaBox plus en CropBox, har fire boxes svarende til CropBox'en

BoxPDFlibPas BoxTypeDefault når fraværendeNedarvelig fra /Pages
MediaBox1Ingen, posten er påkrævetJa
CropBox2MediaBoxJa
BleedBox3CropBoxNej
TrimBox4CropBoxNej
ArtBox5CropBoxNej

To-trins-kæden betyder noget, for CropBox'en kan selv være nedarvet. Effektiv TrimBox for en side, der hverken har en TrimBox eller en CropBox af egen, er CropBox'en hos den nærmeste forfader, der har én, og i mangel deraf det nedarvede MediaBox. Specificationen tilføjer en regel, der er let at glemme: crop-, bleed-, trim- og art-boxes bør ikke strække sig forbi media box, og gør de det, reduceres de effektivt til deres skæring med den. PDFlibPas rapporterer hver box, som den står i filen, så en validator, der håndterer utroværdigt input, bør selv klemme mod MediaBox

PDFlibPas' side-box-default-kæde, hvor CropBox har MediaBox som default og BleedBox, TrimBox og ArtBox hver har CropBox som default, tegnet ved siden af en bogindside med et MediaBox på 450 gange 666 punkter og en CropBox på 432 gange 648 punkter, som bliver den effektive trim, når ingen TrimBox findes
Intet undtagen CropBox har MediaBox som direkte default, så en side med kun et MediaBox har fem identiske boxes

Hvilke sideattributter kan en /Pages-node give videre?

Præcis fire: Resources, MediaBox, CropBox og Rotate. ISO 32000-1 §7.7.3.4 definerer attribut-nedarvning, og Table 30 markerer kun de fire sideobjekt-poster som nedarvelige. BleedBox, TrimBox og ArtBox hører til på bladsiden. En TrimBox skrevet ind i en /Pages-node er ikke en nedarvet værdi; den er en ikke-standard-nøgle, som en standardfølgende reader ignorerer

Ikke-standard filer som den findes, typisk med én TrimBox på rodnoden i sidetræet som stenografi for "alle sider har denne trim". Stenografien ser rigtig ud i ethvert værktøj, der går /Parent for hver nøgle, og det er problemet: filen betyder nu to ting afhængigt af, hvem der læser den. En reader, der følger specificationen, ser ingen TrimBox og bruger CropBox, mens en reader, der nedarver alt, ser forælderværdien. I et prepress-workflow ender den tvetydighed på trykarket

PDFlibPas' sidetræ-nedarvning, hvor kun Resources, MediaBox, CropBox og Rotate gives videre gennem en Pages-node, så en TrimBox parkeret på roden er en ikke-standard-nøgle, som standardfølgende readers ignorerer; før v3.539.44 nedarvede to uafhængige kodeveje den og meldte forskellige trim-størrelser for ét dokument
Filen betyder to ting afhængigt af, hvem der læser den, og i et prepress-workflow lander den tvetydighed på trykarket

PDF/X (ISO 15930)-workflows stoler på TrimBox for færdigstørrelsen, og PDF/X-profilerne kræver, at hver side erklærer en TrimBox eller en ArtBox. En box parkeret på en /Pages-node opfylder ikke det krav, for nøglen når aldrig sideobjektet. Preflight bør markere sådanne filer frem for stille at læse dem på den ene eller anden måde

Hvad tog PDFlibPas fejl af før v3.539.44?

PDFlibPas havde tre separate defekter, alle i kløften mellem, hvad specificationen siger, og hvad to uafhængige kodeveje gjorde. Den første blev rettet i v3.539.42, de to andre i v3.539.44

Production boxes havde MediaBox som default under capture

Før v3.539.42 gav den interne rutine, der klargør en side til capture (den kopierer nedarvede poster over på siden og udfylder manglende boxes), BleedBox, TrimBox og ArtBox MediaBox-værdierne, når de manglede. CapturePageEx med options 2 til 4 læser sin bounding-rectangle fra præcis de udfyldte poster, så på en side, der kun definerer en CropBox, captured spørgsmålet efter trim-boxen hele media box. GetPageBox anvendte allerede CropBox-defaulten, og CapturePageEx-referencen havde altid sagt, at crop box bruges, når den anmodede box mangler; capture-koden var uenig med begge. Siden v3.539.42 har de tre production boxes CropBox'en som default, som på det tidspunkt allerede er på siden (dens egen, kopieret fra en forfader eller udfyldt fra MediaBox), og kun CropBox'en selv falder tilbage til MediaBox

To nedarvningsveje, én semantisk regel

Den anden defekt var den ikke-standard-nedarvning selv, og det subtile var, at PDFlibPas opløste boxes langs to uafhængige veje. Box-forespørgsler (GetPageBox og HasPageBox) gik /Parent-kæden gennem én hjælper, og capture gik den gennem en separat lokal hjælper. Begge nedarvede hver nøgle, production boxes inkluderet. At rette kun én af dem ville have givet en modstrid indeni et enkelt dokument: med en 180-punkter-bred TrimBox på /Pages-noden og en 380-punkter-bred CropBox på siden ville GetPageBox stadig melde en trim-bredde på 180, mens CapturePageEx byggede en form på 380 i bredde. I v3.539.44 begrænser begge veje /Parent-gangen til de fire nedarvelige nøgler, production boxes læses kun fra bladsiden, og den ledige forælder-post bliver i filen urørt, hverken slettet eller omskrevet

PDFlibPas' HasPageBox-returkoder nul, én og to, hvor direkte og indirekte arrays begge tæller som nedarvet siden v3.539.44, ved siden af CapturePageEx' options nul til fire, hvor BleedBox, TrimBox og ArtBox falder tilbage til CropBox i stedet for MediaBox siden v3.539.42
To implementationsindgange for én specificationregel rettes sammen og testes som en matrix på 18 scenarier, med forespørgsel og capture enige om hver fil

HasPageBox overså direkte forælder-arrays

HasPageBox returnerer 0, når siden ingen box af den anmodede type har, 1, når siden har sin egen box (gemt direkte eller gennem en indirekte reference), og 2, når et MediaBox eller CropBox er nedarvet fra en forfader. Den gamle kode returnerede 2, kun når den nedarvede værdi var en indirekte reference, så et nedarvet direkte array returnerede 0. Fixet adskiller dereferering fra array-testen, og begge repræsentationer returnerer nu 2. Siden v3.539.44 kan HasPageBox for en BleedBox, TrimBox eller ArtBox kun returnere 0 eller 1

Lektionen generaliserer langt ud over sideboxes. Når ét stykke specificationsemantik har to implementationsindgange i et bibliotek, så ret dem sammen og test dem som en matrix frem for med én happy path-fil. PDFlibPas' regressionssæt krydser to forælder-box-repræsentationer (direkte og indirekte array) med tre bladtilstande (fraværende, direkte array, indirekte array) og tre capture-options (bleed, trim, art), hvilket giver 18 scenarier, og hvert ét tjekker forespørgselsresultatet, de capturede grænser, legitim MediaBox- og CropBox-nedarvning og den urørte forælder-post

Hvordan læser jeg den effektive TrimBox i Delphi?

Kald GetPageBox(4, Dimension) på den valgte side. PDFlibPas anvender default-kæden for dig, så resultatet er den effektive TrimBox, uanset om siden har én. Par det med HasPageBox, når du behøver at vide, hvor værdien kom fra, hvilket en preflight-rapport normalt gør

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 = egen, 2 = nedarvet
    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.

Både GetPageBox og SetPageBox arbejder i dokumentets aktuelle koordinatindstillinger. Eksemplerne her kører med defaults: origo 0 (nederst til venstre, svarende til PDF user space) og punkter som måleenhed, så Top-dimensionen er den øvre kant målt op fra sidens bund. Efter SetOrigin(1) måles Top- og Bottom-dimensionerne i stedet ned fra sidens top, og efter SetMeasurementUnits(1) kommer hver værdi tilbage i millimeter. Bredde og højde afhænger ikke af origo

At finde production boxes strandet på /Pages-noder

Siden v3.539.44 ser box-API'et ikke længere en TrimBox på en /Pages-node, hvilket er korrekt, men et preflight-værktøj vil typisk melde sådan en fil frem for stille at læse den specificationstro. Sidetræ-noder er almindelige objekter, så det lavniveaus objekt-API kan finde dem: gå objektnumre op til GetMaxObjectNumber, læs hvert med GetObjectToString, og kig efter en /Pages-dictionary, der bærer en production box-nøgle. Anden halvdel af tjekket er den pr. side-test, PDF/X er optaget af, og HasPageBox svarer nu på den, som en PDF/X-validator ville, for en forælder-TrimBox tæller ikke længere

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. Production boxes på sidetræ-noder: ikke-standard og ignoreres
  for ObjNum := 1 to Lib.GetMaxObjectNumber do
  begin
    Src := '';                                // ledige numre returnerer ingen 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: hver side behøver sin egen TrimBox eller 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. Valgfri reparation: en 6 x 9 tommer-trim i en 6,25 x 9,25 tommer media box
  //    (punkter, origo nederst til venstre: Left, Top, Width, Height)
  if Missing > 0 then
    Log.Add(Format('TrimBox written on %d pages',
      [Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;

Tekstmatchet er et pragmatisk tjek, ikke en parser. Det stoler på, at PDFlibPas serialiserer hver dictionary-post som en nøgle, ét mellemrum og en værdi, hvilket holder for objekter læst tilbage gennem GetObjectToString. Reparationstrinnet fortjener en beslutning frem for en refleks: den ledige forælderværdi kan sagtens være, hvad forfatten mente, men bekræft den mod jobbilletterne, inden du gør den officiel. SetPageBoxRange med et tomt interval anvender boxen på alle sider og returnerer antallet af opdaterede sider. Er en sides eksisterende box et indirekte array, som en anden side eller en /Pages-node kan dele, giver SetPageBox den side et nyt direkte array i stedet for at omskrive det delte objekt. At sætte en BleedBox, TrimBox eller ArtBox hæver desuden et ulåst dokument til PDF 1.3, den version, der introducerede de poster

At imposere sider på TrimBox med CapturePageEx

CapturePageEx(Page, 3) omdanner en side til et Form XObject, hvis bounding box er sidens effektive TrimBox, og DrawCapturedPage placerer den form på en anden side i enhver størrelse. Siden v3.539.42 giver option 3 på en side uden TrimBox dig CropBox'en, som referencen beskriver, i stedet for MediaBox med al sin slug

To egenskaber ved capture former koden. Capture er destruktiv: den capturede side fjernes fra dokumentet, og dokumentet kan aldrig falde til nul sider, så tilføj det første output-ark, inden du capturer noget. Capture virker også kun inden for ét dokument, så træk alle inputs ind i ét enkelt dokument først; teknikkerne til at sortere og flette PDF-kilder i én gennemkørsel kan bruges direkte

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;

    // Effektiv trim-størrelse af side 1 (dette layout antager en ensartet trim)
    Lib.SelectPage(1);
    TrimW := Lib.GetPageBox(4, 2);
    TrimH := Lib.GetPageBox(4, 3);

    // Tilføj og sæt størrelse på første ark; NewPage vælger den nye side
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Hver capture fjerner side 1, så næste kildeside rykker op
    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;

    // Kun arket er tilbage: to trimmede sider pr. ark, side om side
    Lib.SelectPage(1);
    for I := 0 to SourceCount - 1 do
    begin
      if (I > 0) and (I mod 2 = 0) then
        Lib.NewPage;                            // samme størrelse som det aktuelle ark
      // Default origo: Top er øvre kant, målt fra bunden
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

En trim-baseret capture klipper alt uden for TrimBox, hvilket er det, du vil have til et digitalt proof eller et cut-and-stack-layout. Til et trykark, der trimmes efter trykning, capture med option 2, så bleed overlever, og fordel cellerne med bleed-bredden. Eftersom capture fjerner kildesiderne, mister bookmarks og links, der pegede på dem, deres targets, så imposér til en separat output-fil i stedet for at redigere et dokument, hvis navigation du stadig behøver; at erstatte sider uden at knække bookmarks dækker den side af page surgery

Skal kilden forblive intakt, tager ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) de samme optionværdier 0 til 4 (giv Lib.SelectedDocument for det aktuelle dokument), lader kildens sidetræ stå uændret, normaliserer nedarvet siderotation ind i form-matrixen og returnerer et handle, som DrawCapturedPage accepterer. CapturePageEx ugør ikke /Rotate, så roteret input behøver det trin først, og at fladtrykke siderotation uden at ødelægge sideboxes viser, hvad der sker med hver box, når du gør det. En advarsel om inputs, der kan bære production boxes på /Pages-noder: importvejen opløser sin box gennem sit eget forfader-opslag, separat fra de to veje, der blev tilpasset i v3.539.44, så tjek HasPageBox(4) på kildesiden først, og giv option 1 (CropBox), når den returnerer 0. Det holder resultatet knyttet til specificationen frem for til, hvordan filen tilfældigvis var skrevet

Side-box hurtig reference

  • Effektiv CropBox: sidens egen CropBox, ellers nærmeste nedarvede CropBox, ellers den effektive MediaBox (ISO 32000-1 §14.11.2)
  • Effektiv BleedBox, TrimBox og ArtBox: bladsidens egen post, ellers den effektive CropBox
  • Kun Resources, MediaBox, CropBox og Rotate nedarver fra /Pages-noder (§7.7.3.4, Table 30); production boxes på /Pages-noder ignoreres
  • 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 ingen box, 1 sidens egen box (direkte eller indirekte), 2 et nedarvet MediaBox eller CropBox (direkte eller indirekte)
  • CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox med MediaBox-fallback, 2 til 4 BleedBox, TrimBox eller ArtBox med CropBox-fallback
  • Opgradér til v3.539.44 eller senere for konsekvente defaults og nedarvning på tværs af box-forespørgsler og capture

Sideboxes er dér, hvor PDF's stille defaults møder prepress-tolerancer målt i brøkdele af en millimeter, og et bibliotek anvender enten de defaults på samme måde overalt eller giver dig to svar på ét spørgsmål. Den fulde box-, capture- og Form XObject-API er dokumenteret på PDFlibPas PDF Library for Delphi-produktsiden