När en PDF-sida saknar TrimBox är dess effektiva TrimBox sidans CropBox, och när CropBox också saknas är det MediaBox. BleedBox och ArtBox följer samma regel. PDFlibPas, PDF Library for Delphi, tillämpar denna standardkedja konsekvent i GetPageBox, HasPageBox och CapturePageEx sedan v3.539.44, och den ignorerar produktionsboxar placerade på en /Pages-nod, för ISO 32000-1 låter dem inte ärva
Det låter som en fotnot tills du imponerar ett jobb. Föreställ dig en bokinsida med en MediaBox på 6,25 × 9,25 tum, en CropBox satt till trimsnittet 6 × 9 tum, och ingen TrimBox, för den som exporterade den tänkte aldrig på att skriva en. Be om trimboxen, få mediaboxen i stället, och varje cell på ditt tryckark släpar med en åttondels tum av utblekning och anslag in i sin granne. PDFlibPas hade defekter i exakt det här området, fixade i v3.539.42 och v3.539.44, och sättet de fixades på säger något om hur sidboxsemantik bör implementeras i vilken PDF-library som helst
Vilken box gäller när en sida saknar TrimBox?
Svaret är en fast standardkedja från ISO 32000-1 §14.11.2: CropBox faller tillbaka på MediaBox, och BleedBox, TrimBox och ArtBox faller var och en tillbaka på CropBox. Inget utom CropBox faller direkt tillbaka på MediaBox. En sida som definierar bara en MediaBox har alltså fem identiska boxar, och en sida som definierar en MediaBox plus en CropBox har fyra boxar lika med CropBox
| Box | PDFlibPas BoxType | Standard vid frånvaro | Ärvbar från /Pages |
|---|---|---|---|
| MediaBox | 1 | Ingen, posten krävs | Ja |
| CropBox | 2 | MediaBox | Ja |
| BleedBox | 3 | CropBox | Nej |
| TrimBox | 4 | CropBox | Nej |
| ArtBox | 5 | CropBox | Nej |
Tvåstegskedjan spelar roll för att CropBox själv kan vara ärvd. Den effektiva TrimBox för en sida utan varken TrimBox eller CropBox av egen är CropBox hos närmaste förfader som har en, och om den misslyckas, den ärvda MediaBox. Specen lägger till ytterligare en regel som är lätt att glömma: crop-, bleed-, trim- och art-boxarna ska inte sträcka sig förbi mediaboxen, och gör de det är de i praktiken reducerade till sin skärning med den. PDFlibPas rapporterar varje box som lagrad i filen, så en validator som hanterar opålitlig indata bör klamra mot MediaBox själv
Vilka sidattribut kan en /Pages-nod föra vidare?
Exakt fyra: Resources, MediaBox, CropBox och Rotate. ISO 32000-1 §7.7.3.4 definierar attributärftning, och Table 30 märker bara de fyra sidobjektsposterna som ärvbara. BleedBox, TrimBox och ArtBox tillhör lövsidan. En TrimBox skriven in i en /Pages-nod är inte ett ärvt värde; den är en icke-standardnyckel som en normföljande läsare ignorerar
Icke-standardfiler som den finns, typiskt med en enda TrimBox på rotsidträdsnoden som förkortning för "varje sida har denna trim". Förkortningen ser rätt ut i vilket verktyg som helst som stegar /Parent för varje nyckel, och det är problemet: filen betyder nu två saker beroende på vem som läser den. En läsare som följer specen ser ingen TrimBox och använder CropBox, medan en läsare som ärver allt ser föräldervärdet. I en repropipeline hamnar den tvetydigheten på tryckarket
PDF/X-arbetsflöden (ISO 15930) förlitar sig på TrimBox för färdig storlek, och PDF/X-profilerna kräver att varje sida deklarerar en TrimBox eller en ArtBox. En box parkerad på en /Pages-nod uppfyller inte det kravet, för nyckeln når aldrig sidobjektet. Preflight bör flagga sådana filer i stället för att tyst läsa dem ena eller andra vägen
Vad gjorde PDFlibPas fel före v3.539.44?
PDFlibPas hade tre separata defekter, alla i gapet mellan vad specen säger och vad två oberoende kodvägar gjorde. Den första fixades i v3.539.42, de andra två i v3.539.44
Produktionsboxar fick MediaBox som standard vid fångst
Före v3.539.42 gav den interna rutinen som förbereder en sida för fångst (den kopierar ärvda poster på sidan och fyller i saknade boxar) BleedBox, TrimBox och ArtBox MediaBox-värdena när de saknades. CapturePageEx med alternativ 2 till 4 läser sin avgränsande rektangel från exakt de ifyllda posterna, så på en sida som definierar bara en CropBox fångade en förfrågan om trimboxen hela mediaboxen. GetPageBox tillämpade redan CropBox-standarden, och CapturePageEx-referensen hade alltid sagt att crop box används när den begärda boxen saknas; fångstkoden motsade båda. Sedan v3.539.42 får de tre produktionsboxarna sidans CropBox som standard, som vid den punkten redan ligger på sidan (dess egen, kopierad från en förfader, eller ifylld från MediaBox), och bara CropBox själv faller tillbaka på MediaBox
Två ärftningsvägar, en semantisk regel
Den andra defekten var den icke-standardmässiga ärftningen själv, och det subtila var att PDFlibPas löste upp boxar längs två oberoende vägar. Boxfrågor (GetPageBox och HasPageBox) stegade /Parent-kedjan genom en hjälpare, och fångsten stegade den genom en separat lokal hjälpare. Båda ärvde varje nyckel, produktionsboxar inkluderade. Att fixa bara en av dem skulle ha framställt en motsägelse inuti ett enda dokument: med en TrimBox 180 punkter bred på /Pages-noden och en CropBox 380 punkter bred på sidan skulle GetPageBox fortfarande rapportera en trimbredd på 180 medan CapturePageEx byggde en 380 bred form. I v3.539.44 begränsar båda vägarna /Parent-stegningen till de fyra ärvbara nycklarna, produktionsboxar läses bara från lövet, och den vilsekomna föräldraposten stannar i filen orörd, vare sig raderad eller omskriven
HasPageBox missade direkta förälderarrayer
HasPageBox returnerar 0 när sidan saknar en box av begärd typ, 1 när sidan har en egen box (lagrad direkt eller via en indirekt referens), och 2 när en MediaBox eller CropBox ärvs från en förfader. Gamla koden returnerade 2 bara när det ärvda värdet var en indirekt referens, så en ärvd direkt array returnerade 0. Fixen skiljer dereferering från arraytestet, och båda representationerna returnerar nu 2. Sedan v3.539.44 kan HasPageBox för en BleedBox, TrimBox eller ArtBox bara returnera 0 eller 1
Lärdomen generaliserar långt bortom sidboxar. När en bit specsemantik har två implementeringsingångar i ett bibliotek, fixa dem tillsammans och testa dem som en matris i stället för med en gladvägsfil. PDFlibPas regressionsmängd korsar två förälderboxrepresentationer (direkt och indirekt array) med tre lövtillstånd (frånvarande, direkt array, indirekt array) och tre fångstalternativ (bleed, trim, art), vilket ger 18 scenarier, och var och en kontrollerar frågeresultatet, de fångade gränserna, legitim MediaBox- och CropBox-ärftning, och den orörda föräldraposten
Hur läser jag den effektiva TrimBox i Delphi?
Anropa GetPageBox(4, Dimension) på den valda sidan. PDFlibPas tillämpar standardkedjan åt dig, så resultatet är den effektiva TrimBox oavsett om sidan har en eller inte. Para den med HasPageBox när du behöver veta var värdet kom ifrån, vilket en preflightrapport vanligen 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 = ärvd
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 och SetPageBox arbetar i dokumentets aktuella koordinatinställningar. Exemplen här kör med standarderna: origo 0 (nere till vänster, matchande PDF user space) och punkter som mätenhet, så dimensionen Top är den övre kanten mätt uppåt från sidans nederkant. Efter SetOrigin(1) mäts dimensionerna Top och Bottom nedåt från sidans topp i stället, och efter SetMeasurementUnits(1) kommer varje värde tillbaka i millimeter. Bredd och höjd beror inte på origo
Hitta produktionsboxar strandsatta på /Pages-noder
Sedan v3.539.44 ser box-API:t inte längre en TrimBox på en /Pages-nod, vilket är korrekt, men ett preflight-verktyg vill vanligen rapportera en sådan fil i stället för att tyst läsa den på specens sätt. Sidträdsnoder är vanliga objekt, så lågnivå-objekt-API:et kan hitta dem: stega objektnummer upp till GetMaxObjectNumber, läs varje med GetObjectToString, och leta efter en /Pages-ordbok som bär en produktionsboxnyckel. Andra halvan av kontrollen är sidvis-testet PDF/X bryr sig om, och HasPageBox svarar den nu som en PDF/X-validator skulle, för en förälder-TrimBox räknas inte längre
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. Produktionsboxar på sidträdsnoder: icke-standard och ignoreras
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // lediga nummer returnerar ingen 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: varje sida behöver en 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. Valfri reparation: en trim på 6 x 9 tum inuti en mediabox på 6.25 x 9.25 tum
// (punkter, ursprung nere vänster: Left, Top, Width, Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
Textmatchningen är en pragmatisk kontroll, inte en parser. Den förlitar sig på att PDFlibPas serialiserar varje ordbokspost som en nyckel, ett mellanslag och ett värde, vilket gäller objekt lästa tillbaka via GetObjectToString. Reparationssteget förtjänar ett beslut i stället för en reflex: det vilsekomna föräldervärdet kan mycket väl vara vad upphovsmannen avsåg, men bekräfta det mot jobbiljetten innan du gör det officiellt. SetPageBoxRange med ett tomt intervall tillämpar boxen på varje sida och returnerar antalet uppdaterade sidor. När en sidas befintliga box är en indirekt array, som en annan sida eller en /Pages-nod kan dela, ger SetPageBox den sidan en ny direkt array i stället för att skriva om det delade objektet. Att sätta en BleedBox, TrimBox eller ArtBox höjer också ett olåst dokument till PDF 1.3, versionen som introducerade de posterna
Imponecera sidor på TrimBox med CapturePageEx
CapturePageEx(Page, 3) gör en sida till en Form XObject vars avgränsande box är sidans effektiva TrimBox, och DrawCapturedPage placerar den formen på en annan sida i valfri storlek. Sedan v3.539.42 ger alternativ 3 på en sida utan TrimBox dig CropBox, som referensen beskriver, i stället för MediaBox med allt sitt anslag
Två egenskaper hos fångsten formar koden. Fångst är destruktiv: den fångade sidan tas bort ur dokumentet, och dokumentet kan aldrig sjunka till noll sidor, så lägg till första utdataarket innan du fångar något. Fångst fungerar dessutom bara inom ett dokument, så dra in all indata i ett enda dokument först; teknikerna för att sammanfoga och fläta PDF-källor i ett pass tillämpas direkt
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 trimstorlek för sida 1 (denna layout antar en enhetlig trim)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// Lägg till och storleksätt första arket; NewPage väljer den nya sidan
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// Varje fångst tar bort sida 1, så nästa källsida flyttar upp
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;
// Bara arket är kvar: två trimmade sidor per ark, sida vid sida
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // samma storlek som aktuellt ark
// Standardursprung: Top är övre kant, mätt från nederkanten
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
En trimbaserad fångst klipper allt utanför TrimBox, vilket är vad du vill ha för ett digitalt korrektur eller en cut-and-stack-layout. För ett tryckark som trimmas efter tryck, fånga med alternativ 2 så att utblekningen överlever, och avstå cellerna med utblekningsbredden. Eftersom fångsten tar bort källsidorna tappar bokmärken och länkar som pekade på dem sina mål, så imponecera till en separat utdatafil i stället för att redigera ett dokument vars navigation du fortfarande behöver; att byta ut sidor utan att bryta bokmärken täcker den sidan av sidkirurgi
När källan måste stanna intakt tar ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) samma alternativvärden 0 till 4 (skicka Lib.SelectedDocument för aktuellt dokument), lämnar källans sidträd orört, normaliserar ärvd sidrotation in i formmatrisen, och returnerar ett handtag som DrawCapturedPage accepterar. CapturePageEx gör inte ogjort /Rotate, så roterad indata behöver det steget först, och att flattena sidrotation utan att bryta sidboxar visar vad som händer med varje box när du gör det. En varning för indata som kan bära produktionsboxar på /Pages-noder: importvägen löser upp sin box via sin egen förfaderuppslagning, skild från de två vägarna riktade i v3.539.44, så kontrollera HasPageBox(4) på källsidan först och skicka alternativ 1 (CropBox) när den returnerar 0. Det håller resultatet knutet till specen i stället för till hur filen råkade vara skriven
Snabbreferens för sidboxar
- Effektiv CropBox: sidans egen CropBox, annars närmaste ärvda CropBox, annars den effektiva MediaBox (ISO 32000-1 §14.11.2)
- Effektiv BleedBox, TrimBox och ArtBox: lövsidans egen post, annars den effektiva CropBox
- Bara
Resources,MediaBox,CropBoxochRotateärver från/Pages-noder (§7.7.3.4, Table 30); produktionsboxar på/Pages-noder ignoreras 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 sidans egen box (direkt eller indirekt), 2 en ärvd MediaBox eller CropBox (direkt eller indirekt)CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox med MediaBox-fallback, 2 till 4 BleedBox, TrimBox eller ArtBox med CropBox-fallback- Uppgradera till v3.539.44 eller senare för konsekventa standarder och ärftning över boxfrågor och fångst
Sidboxar är där PDF:s tysta standarder möter reproductionstoleranser mätta i bråkdelar av en millimeter, och ett bibliotek tillämpar antingen de standarderna på samma sätt överallt eller ger dig två svar på en fråga. Det fullständiga box-, fångst- och Form XObject-API:et är dokumenterat på produktsidan för PDFlibPas PDF Library for Delphi