Teknisk artikkel

PDFlibPas sidebokser: TrimBox, BleedBox, CropBox-standarder

Når en PDF-side ikke har TrimBox, er dens effektive TrimBox sidens CropBox, og når CropBox også mangler, er det MediaBox. BleedBox og ArtBox følger samme regel. PDFlibPas, PDF Library for Delphi, anvender denne standardkjeden konsekvent i GetPageBox, HasPageBox og CapturePageEx siden v3.539.44, og den ignorerer produksjonsbokser plassert på en /Pages-node, for ISO 32000-1 lar dem ikke arve

Det høres ut som en fotnote til du legger ut en jobb. Tenk deg en bokinnside med en 6,25 × 9,25 tommer MediaBox, en CropBox satt til 6 × 9 tommer trim, og ingen TrimBox, for den som eksporterte den, tenkte aldri på å skrive én. Be om trimboksen, få medieboksen i stedet, og hver celle på trykkarket sleper en åttendedel tomme med bleed og slug inn i naboen. PDFlibPas hadde defekter i nøyaktig dette området, fikset i v3.539.42 og v3.539.44, og måten de ble fikset på, sier noe om hvordan sideboks-semantikk bør implementeres i ethvert PDF-bibliotek

Hvilken boks gjelder når en side ikke har TrimBox?

Svaret er en fast standardkjede fra ISO 32000-1 §14.11.2: CropBox faller tilbake på MediaBox, og BleedBox, TrimBox og ArtBox faller hver tilbake på CropBox. Ingenting unntatt CropBox faller rett tilbake på MediaBox. En side som definerer bare en MediaBox, har dermed fem identiske bokser, og en side som definerer en MediaBox pluss en CropBox, har fire bokser lik CropBox

BoksPDFlibPas BoxTypeStandard ved fraværArvbar fra /Pages
MediaBox1Ingen, oppføringen er påkrevdJa
CropBox2MediaBoxJa
BleedBox3CropBoxNei
TrimBox4CropBoxNei
ArtBox5CropBoxNei

Totrinnskjeden betyr noe fordi CropBox selv kan være arvet. Den effektive TrimBox-en til en side utenkenken TrimBox eller CropBox av sin egen er CropBox-en til den nærmeste forfaren som har én, og i mangel av det, den arvede MediaBox-en. Spesifikasjonen legger til én regel til som er lett å glemme: crop-, bleed-, trim- og art-boksene skal ikke strekke seg forbi medieboksen, og gjør de det, reduseres de i praksis til sitt snitt med den. PDFlibPas rapporterer hver boks som lagret i filen, så en validator som håndterer utruelig input, bør klemme mot MediaBox-en selv

PDFlibPas sideboks-standardkjede der CropBox faller tilbake på MediaBox, og BleedBox, TrimBox og ArtBox hver faller tilbake på CropBox, tegnet ved siden av en bokinnside med en 450 ganger 666 punkt MediaBox og en 432 ganger 648 punkt CropBox som blir den effektive trimmen når ingen TrimBox finnes
Ingenting unntatt CropBox faller rett tilbake på MediaBox, så en side med bare en MediaBox har fem identiske bokser

Hvilke sideattributter kan en /Pages-node gi videre?

Nøyaktig fire: Resources, MediaBox, CropBox og Rotate. ISO 32000-1 §7.7.3.4 definerer attributtarv, og Table 30 merker bare de fire sideobjekt-oppføringene som arvbare. BleedBox, TrimBox og ArtBox tilhører bladsiden. En TrimBox skrevet inn i en /Pages-node er ikke en arvet verdi; den er en ikke-standard-nøkkel som en spesifikasjonskonform leser ignorerer

Slike ikke-standard-filer finnes, typisk med en enkelt TrimBox på roten til page tree-en som stenografi for «hver side har denne trimmen». Stenografien ser riktig ut i ethvert verktøy som går /Parent for hver nøkkel, og det er problemet: filen betyr nå to ting avhengig av hvem som leser den. En leser som følger spesifikasjonen, ser ingen TrimBox og bruker CropBox, mens en leser som arver alt, ser forelderverdien. I en prepress-pipeline ender den tvetydigheten på trykkarket

PDFlibPas page tree-arv der bare Resources, MediaBox, CropBox og Rotate går ned gjennom en Pages-node, så en TrimBox parkert på roten er en ikke-standard-nøkkel som konforme lesere ignorerer; før v3.539.44 arvet to uavhengige kode-stier den og rapporterte ulike trimstørrelser for ett dokument
Filen betyr to ting avhengig av hvem som leser den, og i en prepress-pipeline lander den tvetydigheten på trykkarket

PDF/X (ISO 15930)-arbeidsflyter er avhengige av TrimBox for ferdig størrelse, og PDF/X-profilene krever at hver side deklarerer en TrimBox eller en ArtBox. En boks parkert på en /Pages-node oppfyller ikke det kravet, for nøkkelen når aldri sideobjektet. Preflight bør flagge slike filer i stedet for å lese dem i stillhet den ene eller andre veien

Hva gjorde PDFlibPas galt før v3.539.44?

PDFlibPas hadde tre separate defekter, alle i gapet mellom det spesifikasjonen sier og det to uavhengige kode-stier gjorde. Den første ble fikset i v3.539.42, de to andre i v3.539.44

Produksjonsbokser falt tilbake på MediaBox under capture

Før v3.539.42 ga den interne rutinen som klargjør en side for capture (den kopierer arvede oppføringer på siden og fyller inn manglende bokser), BleedBox, TrimBox og ArtBox MediaBox-verdiene når de manglet. CapturePageEx med alternativene 2 til 4 leser sin bounding-rectangle fra nøyaktig de utfylte oppføringene, så på en side som definerer bare en CropBox, ga en forespørsel etter trimboksen hele medieboksen i capture. GetPageBox anvendte allerede CropBox-standardverdien, og CapturePageEx-referansen hadde alltid sagt at crop-boksen brukes når den etterspurte boksen mangler; capture-koden var uenig med begge. Siden v3.539.42 faller de tre produksjonsboksene tilbake på sidens CropBox, som på det punktet allerede ligger på siden (dens egen, kopiert fra en forfar, eller fylt inn fra MediaBox), og bare CropBox selv faller tilbake på MediaBox

To arve-stier, én semantisk regel

Den andre defekten var den ikke-standard-arven i seg selv, og det subtile var at PDFlibPas løste bokser langs to uavhengige stier. Boks-oppslag (GetPageBox og HasPageBox) gikk /Parent-kjeden gjennom én hjelper, og capture gikk den gjennom en separat lokal hjelper. Begge arvet hver nøkkel, produksjonsbokser inkludert. Å fikse bare én av dem ville ha produsert en motsigelse inne i ett enkelt dokument: med en 180-punkts-bred TrimBox på /Pages-noden og en 380-punkts-bred CropBox på siden, ville GetPageBox fortsatt rapportert en trimbredde på 180 mens CapturePageEx bygde et 380-bredt form. I v3.539.44 begrenser begge stier /Parent-gangen til de fire arvbare nøklene, produksjonsbokser leses bare fra bladet, og den hakende forelderoppføringen blir i filen urørt, verken slettet eller omskrevet

PDFlibPas HasPageBox-returkoder null, én og to der direkte og indirekte arrayer begge teller som arvet siden v3.539.44, ved siden av CapturePageEx-alternativene null til fire der BleedBox, TrimBox og ArtBox faller tilbake på CropBox i stedet for MediaBox siden v3.539.42
To implementasjonsinnganger for én spesifikasjonsregel fikses sammen og testes som en matrise på 18 scenarier, med oppslag og capture enige om hver fil

HasPageBox savnet direkte forelderarrayer

HasPageBox returnerer 0 når siden ikke har noen boks av den etterspurte typen, 1 når siden har sin egen boks (lagret direkte eller gjennom en indirekte referanse), og 2 når en MediaBox eller CropBox er arvet fra en forfar. Den gamle koden returnerte 2 bare når den arvede verdien var en indirekte referanse, så en arvet direkte array returnerte 0. Fiksen skiller dereferering fra array-testen, og begge representasjonene returnerer nå 2. Siden v3.539.44 kan HasPageBox for en BleedBox, TrimBox eller ArtBox bare returnere 0 eller 1

Lærdommen generaliserer langt utover sidebokser. Når én bit spesifikasjonssemantikk har to implementasjonsinnganger i et bibliotek, fiks dem sammen og test dem som en matrise i stedet for med én happy path-fil. PDFlibPas-regresjonssettet krysser to forelderboks-representasjoner (direkte og indirekte array) med tre bladtilstander (fraværende, direkte array, indirekte array) og tre capture-alternativer (bleed, trim, art), noe som gir 18 scenarier, og hvert enkelt sjekker oppslagsresultatet, de fangede grensene, legitim MediaBox- og CropBox-arv, og den urørte forelderoppføringen

Hvordan leser jeg den effektive TrimBox-en i Delphi?

Kall GetPageBox(4, Dimension) på den valgte siden. PDFlibPas anvender standardkjeden for deg, så resultatet er den effektive TrimBox-en enten siden har en eller ikke. Kombiner den med HasPageBox når du trenger å vite hvor verdien kom fra, noe en preflight-rapport som regel gjø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 = arvet
    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 arbeider i dokumentets nåværende koordinatinnstillinger. Eksemplene her kjører med standardverdiene: origo 0 (nede til venstre, som matcher PDF user space) og punkter som måleenhet, så Top-dimensjonen er den øvre kanten målt opp fra sidens bunn. Etter SetOrigin(1) måles Top- og Bottom-dimensjonene ned fra sidens topp i stedet, og etter SetMeasurementUnits(1) kommer hver verdi tilbake i millimeter. Bredde og høyde er ikke avhengige av origo

Finne produksjonsbokser som står strandede på /Pages-noder

Siden v3.539.44 ser boks-API-en ikke lenger en TrimBox på en /Pages-node, noe som er korrekt, men et preflight-verktøy vil som regel rapportere en slik fil i stedet for å lese den i stillhet på spesifikasjonsmåten. Page tree-noder er ordinære objekter, så lavnivå objekt-API-et kan finne dem: gå objektnumre opp til GetMaxObjectNumber, les hvert med GetObjectToString, og let etter en /Pages-ordbok som bærer en produksjonsboks-nøkkel. Andre halvdel av sjekken er per-side-testen PDF/X bryr seg om, og HasPageBox svarer den nå slik en PDF/X-validator ville gjort, for en forelder-TrimBox teller ikke lenger

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. Produksjonsbokser på page tree-noder: ikke-standard og ignorert
  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 trenger 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 reparasjon: en 6 x 9 tommer trim inne i en 6.25 x 9.25 tommer mediaboks
  //    (punkter, origo nede 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;

Tekstmatchen er en pragmatisk sjekk, ikke en parser. Den er avhengig av at PDFlibPas serialiserer hver ordbokoppføring som en nøkkel, ett mellomrom og en verdi, noe som holder for objekter lest tilbake gjennom GetObjectToString. Reparasjonssteget fortjener en beslutning snarere enn en refleks: den hakende forelderverdien kan godt være det forfatteren mente, men bekreft den mot jobbseddelen før du gjør den offisiell. SetPageBoxRange med et tomt område anvender boksen på hver side og returnerer antallet oppdaterte sider. Når en sides eksisterende boks er en indirekte array, som en annen side eller en /Pages-node kan dele, gir SetPageBox den siden en ny direkte array i stedet for å omskrive det delte objektet. Å sette en BleedBox, TrimBox eller ArtBox hever også et ulåst dokument til PDF 1.3, versjonen som innførte de oppføringene

Legge ut sider på TrimBox-en med CapturePageEx

CapturePageEx(Page, 3) gjør en side om til et Form XObject hvis bounding box er sidens effektive TrimBox, og DrawCapturedPage plasserer det formet på en annen side i hvilken som helst størrelse. Siden v3.539.42 gir alternativ 3 på en side uten TrimBox deg CropBox, slik referansen beskriver, i stedet for MediaBox med all sin slug

To egenskaper ved capture former koden. Capture er destruktiv: den fangede siden fjernes fra dokumentet, og dokumentet kan aldri falle til null sider, så legg til det første outputarket før du capturerer noe. Capture virker også bare innenfor ett dokument, så trekk alle input inn i ett enkelt dokument først; teknikkene for å sortere og flette PDF-kilder i én gjennomgang gjelder 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 trimstørrelse av side 1 (dette oppsettet antar en uniform trim)
    Lib.SelectPage(1);
    TrimW := Lib.GetPageBox(4, 2);
    TrimH := Lib.GetPageBox(4, 3);

    // Legg til og dimensjoner det første arket; NewPage velger den nye siden
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Hvert capture fjerner side 1, så neste kildeside rykker opp
    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;

    // Bare arket er igjen: to beskårete sider per 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 nåværende arket
      // Standard origo: Top er øvre kant, målt fra bunnen
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

En trim-basert capture klipper alt utenfor TrimBox-en, som er det du vil ha for en digital proof eller et cut-and-stack-oppsett. For et trykkark som beskjæres etter trykking, capture med alternativ 2 slik at bleed-en overlever, og plasser cellene med bleed-bredden som avstand. Fordi capture fjerner kildesidene, mister bokmerker og lenker som pekte på dem målene sine, så legg ut i en separat outputfil i stedet for å redigere et dokument hvis navigasjon du fortsatt trenger; å erstatte sider uten å ødelegge bokmerker dekker den siden av sidekirurgi

Når kilden må forbli intakt, tar ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) de samme alternativverdiene 0 til 4 (send Lib.SelectedDocument for det nåværende dokumentet), lar kildens page tree være uendret, normaliserer arvet siderotasjon inn i form-matrisen, og returnerer et håndtak DrawCapturedPage godtar. CapturePageEx ugjør ikke /Rotate, så rotert input trenger det steget først, og å flate ut siderotasjon uten å ødelegge sidebokser viser hva som skjer med hver boks når du gjør det. Én advarsel for input som kan bære produksjonsbokser på /Pages-noder: importstien løser sin boks gjennom sitt eget forfar-oppslag, atskilt fra de to stiene tilrettelagt i v3.539.44, så sjekk HasPageBox(4) på kildesiden først og send alternativ 1 (CropBox) når den returnerer 0. Det holder resultatet knyttet til spesifikasjonen i stedet for til hvordan filen tilfeldigvis var skrevet

Sideboks-hurtigreferanse

  • Effektiv CropBox: sidens egen CropBox, ellers nærmeste arvede CropBox, ellers den effektive MediaBox (ISO 32000-1 §14.11.2)
  • Effektiv BleedBox, TrimBox og ArtBox: bladsidens egen oppføring, ellers den effektive CropBox
  • Bare Resources, MediaBox, CropBox og Rotate arver fra /Pages-noder (§7.7.3.4, Table 30); produksjonsbokser 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 boks, 1 sidens egen boks (direkte eller indirekte), 2 en arvet 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
  • Oppgrader til v3.539.44 eller senere for konsekvente standardverdier og arv på tvers av boks-oppslag og capture

Sidebokser er der PDFs stille standardverdier møter prepress-toleranser målt i brøkdeler av en millimeter, og et bibliotek anvender enten de standardverdiene på samme måte overalt eller gir deg to svar på ett spørsmål. Hele boks-, capture- og Form XObject-API-et er dokumentert på PDFlibPas PDF Library for Delphi produktsiden