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
| Box | PDFlibPas BoxType | Default når fraværende | Nedarvelig fra /Pages |
|---|---|---|---|
| MediaBox | 1 | Ingen, posten er påkrævet | Ja |
| CropBox | 2 | MediaBox | Ja |
| BleedBox | 3 | CropBox | Nej |
| TrimBox | 4 | CropBox | Nej |
| ArtBox | 5 | CropBox | Nej |
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
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
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
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,CropBoxogRotatenedarver 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 BottomHasPageBox(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