Heeft een PDF-pagina geen TrimBox, dan is zijn effectieve TrimBox de CropBox van de pagina, en ontbreekt ook de CropBox, dan is het de MediaBox. BleedBox en ArtBox volgen dezelfde regel. PDFlibPas, de PDF Library for Delphi, past deze default-keten sinds v3.539.44 consistent toe in GetPageBox, HasPageBox en CapturePageEx, en hij negeert production boxes die op een /Pages-node staan, want ISO 32000-1 laat ze niet overerven
Dat klinkt als een voetnoot tot u een job imponeert. Denk aan een boekbinnenwerk met een MediaBox van 6,25 × 9,25 inch, een CropBox ingesteld op de trim van 6 × 9 inch, en geen TrimBox, want wie het exporteerde is nooit op het idee gekomen er een weg te schrijven. Vraag om de trimbox, krijg de mediabox, en elke cel op uw drukvel sleept een achtste inch bleed en slug mee richting buurman. PDFlibPas had op precies dit terrein gebreken, opgelost in v3.539.42 en v3.539.44, en de manier waarop ze zijn opgelost zegt iets over hoe paginabox-semantiek in elke PDF-library hoort te worden geïmplementeerd
Welke box geldt als een pagina geen TrimBox heeft?
Het antwoord is een vaste default-keten uit ISO 32000-1 §14.11.2: de CropBox valt terug op de MediaBox, en de BleedBox, TrimBox en ArtBox vallen elk terug op de CropBox. Niets behalve de CropBox valt rechtstreeks terug op de MediaBox. Een pagina die alleen een MediaBox definieert heeft dus vijf identieke boxes, en een pagina die een MediaBox plus een CropBox definieert heeft vier boxes gelijk aan de CropBox
| Box | PDFlibPas BoxType | Default bij afwezigheid | Overerfbaar van /Pages |
|---|---|---|---|
| MediaBox | 1 | Geen, de entry is vereist | Ja |
| CropBox | 2 | MediaBox | Ja |
| BleedBox | 3 | CropBox | Nee |
| TrimBox | 4 | CropBox | Nee |
| ArtBox | 5 | CropBox | Nee |
De keten van twee stappen telt omdat de CropBox zelf kan overerven. De effectieve TrimBox van een pagina met geen eigen TrimBox en geen eigen CropBox is de CropBox van de dichtstbijzijnde voorouder die er een heeft, en bij gebreke daarvan de overgeërfde MediaBox. De spec voegt nog één regel toe die makkelijk te vergeten is: de crop-, bleed-, trim- en art-boxen horen niet voorbij de mediabox te reiken, en doen ze dat wel, dan worden ze in feite gereduceerd tot hun snijding ermee. PDFlibPas meldt elke box zoals hij in het bestand staat, dus een validator die niet-vertrouwde invoer verwerkt moet zelf tegen de MediaBox klemmen
Welke pagina-attributen kan een /Pages-node doorgeven?
Precies vier: Resources, MediaBox, CropBox en Rotate. ISO 32000-1 §7.7.3.4 definieert attribuuterfenis, en Table 30 merkt alleen die vier page-object-entries als overerfbaar aan. BleedBox, TrimBox en ArtBox horen bij de leafpagina. Een TrimBox weggeschreven in een /Pages-node is geen overgeërfde waarde; het is een niet-standaard key die een conforming reader negeert
Zo'n niet-standaard bestand bestaat echt, doorgaans met één TrimBox op de root van de paginaboom als stenografie voor "elke pagina heeft deze trim". De stenografie ziet er goed uit in elke tool die voor elke key /Parent doorloopt, en dat is het probleem: het bestand betekent nu twee dingen naargelang wie het leest. Een lezer die de spec volgt ziet geen TrimBox en gebruikt de CropBox, terwijl een lezer die alles laat overerven de parentwaarde ziet. In een prepress-pijplijn eindigt die ambiguïteit op het drukvel
PDF/X-workflows (ISO 15930) rekenen op de TrimBox voor het eindformaat, en de PDF/X-profielen eisen dat elke pagina een TrimBox of een ArtBox verklaart. Een box geparkeerd op een /Pages-node voldoet niet aan die eis, want de key bereikt het paginaobject nooit. Preflight hoort zulke bestanden te vlaggen in plaats van ze stilletjes de ene of de andere kant op te lezen
Wat deed PDFlibPas verkeerd vóór v3.539.44?
PDFlibPas had drie aparte gebreken, alle drie in de kloof tussen wat de spec zegt en wat twee onafhankelijke codepaden deden. De eerste werd opgelost in v3.539.42, de andere twee in v3.539.44
Production boxes vielen bij capture terug op de MediaBox
Vóór v3.539.42 gaf de interne routine die een pagina klaarmaakt voor capture (ze kopieert overgeërfde entries op de pagina en vult ontbrekende boxes aan) de BleedBox, TrimBox en ArtBox de MediaBox-waarden zodra ze ontbraken. CapturePageEx met opties 2 tot 4 leest zijn begrenzende rechthoek uit precies die aangevulde entries, dus op een pagina die alleen een CropBox definieert, ving de vraag om de trimbox de hele mediabox in. GetPageBox paste de CropBox-default al toe, en de CapturePageEx-referentie zei altijd al dat de crop box wordt gebruikt zodra de gevraagde box ontbreekt; de capture-code vlooide met beide. Sinds v3.539.42 vallen de drie production boxes terug op de CropBox van de pagina, die op dat punt al op de pagina staat (eigen, gekopieerd van een voorouder, of aangevuld vanuit de MediaBox), en alleen de CropBox zelf valt terug op de MediaBox
Twee erfenispaden, één semantische regel
Het tweede gebrek was de niet-standaard erfenis zelf, en het subtiele was dat PDFlibPas boxes langs twee onafhankelijke paden resolveerde. Boxqueries (GetPageBox en HasPageBox) doorliepen de /Parent-keten via één helper, en capture via een aparte lokale helper. Beide lieten elke key overerven, production boxes inbegrepen. Er er maar één van fixen zou een contradictie binnen één document hebben opgeleverd: met een TrimBox van 180 punten breed op de /Pages-node en een CropBox van 380 punten breed op de pagina, zou GetPageBox nog steeds een trimbreedte van 180 melden terwijl CapturePageEx een form van 380 breed bouwde. In v3.539.44 beperken beide paden de /Parent-wandeling tot de vier overerfbare keys, worden production boxes alleen van de leaf gelezen, en blijft de verdwaalde parent-entry onaangetast in het bestand, noch verwijderd noch herschreven
HasPageBox miste directe parent-arrays
HasPageBox geeft 0 terug als de pagina geen box van het gevraagde type heeft, 1 als de pagina een eigen box heeft (direct opgeslagen of via een indirecte verwijzing), en 2 als een MediaBox of CropBox van een voorouder erft. De oude code gaf 2 alleen terug als de overgeërfde waarde een indirecte verwijzing was, dus een overgeërfde directe array gaf 0. De fix scheidt dereferentiëren van de arraytest, en beide vormen geven nu 2. Sinds v3.539.44 kan HasPageBox voor een BleedBox, TrimBox of ArtBox alleen 0 of 1 teruggeven
De les generaliseert ruim voorbij paginaboxen. Wanneer één stuk spec-semantiek twee implementatie-ingangspunten heeft in een library, fix ze dan samen en test ze als matrix in plaats van met één happy-path-bestand. De PDFlibPas-regressieset kruist twee parent-box-vormen (directe en indirecte array) met drie leaf-states (afwezig, directe array, indirecte array) en drie capture-opties (bleed, trim, art), wat 18 scenario's oplevert, en elk daarvan controleert het queryresultaat, de gevangen grenzen, de legitieme MediaBox- en CropBox-erfenis, en de onaangeroerde parent-entry
Hoe lees ik de effectieve TrimBox in Delphi?
Roep GetPageBox(4, Dimension) aan op de geselecteerde pagina. PDFlibPas past de default-keten voor u toe, dus het resultaat is de effectieve TrimBox of de pagina er nu een heeft of niet. Koppel hem aan HasPageBox wanneer u moet weten waar de waarde vandaan kwam, wat een preflightrapport doorgaans wil
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 = eigen, 2 = overgeërfd
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 en SetPageBox werken allebei in de huidige coördinaatinstellingen van het document. De voorbeelden hier draaien met de defaults: origine 0 (linksonder, gelijk aan PDF user space) en punten als meeteenheid, dus de Top-dimensie is de bovenrand gemeten opwaarts vanaf de onderrand van de pagina. Na SetOrigin(1) worden de dimensies Top en Bottom neerwaarts vanaf de bovenkant van de pagina gemeten, en na SetMeasurementUnits(1) komt elke waarde terug in millimeters. Breedte en hoogte hangen niet van de origine af
Production boxes vinden die op /Pages-nodes zijn gestrand
Sinds v3.539.44 ziet de box-API geen TrimBox op een /Pages-node meer, wat klopt, maar een preflighttool wil zo'n bestand meestal melden in plaats van het stilletjes op de spec-manier te lezen. Paginaboom-nodes zijn gewone objecten, dus de low-level object-API kan ze vinden: loop de objectnummers op tot GetMaxObjectNumber, lees elk met GetObjectToString, en zoek naar een /Pages-dictionary die een production-box-key voert. De tweede helft van de controle is de per-paginatoets waar PDF/X om geeft, en HasPageBox beantwoordt die nu zoals een PDF/X-validator zou doen, want een parent-TrimBox telt niet meer
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 op paginaboom-nodes: niet-standaard en genegeerd
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // vrije nummers geven geen tekst terug
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: elke pagina heeft een eigen TrimBox of ArtBox nodig
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. Optionele reparatie: een trim van 6 x 9 inch in een mediabox van
// 6.25 x 9.25 inch (punten, origine linksonder: Left, Top, Width, Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
De tekstmatch is een pragmatische controle, geen parser. Hij vertrouwt erop dat PDFlibPas elke dictionary-entry serialiseert als key, één spatie en een waarde, wat opgaat voor objecten die via GetObjectToString worden teruggelezen. De reparatiestap verdient een beslissing in plaats van een reflex: de verdwaalde parentwaarde is misschien precies wat de auteur bedoelde, maar toets haar aan de jobticket voordat u haar officieel maakt. SetPageBoxRange met een lege range past de box op elke pagina toe en geeft het aantal bijgewerkte pagina's terug. Is de bestaande box van een pagina een indirecte array, die een andere pagina of een /Pages-node kan delen, dan geeft SetPageBox die pagina een nieuwe directe array in plaats van het gedeelde object te herschrijven. Het zetten van een BleedBox, TrimBox of ArtBox tilt een niet-vergrendeld document bovendien op naar PDF 1.3, de versie die die entries introduceerde
Pagina's imputeren op de TrimBox met CapturePageEx
CapturePageEx(Page, 3) maakt van een pagina een Form XObject waarvan de begrenzende box de effectieve TrimBox van de pagina is, en DrawCapturedPage zet die form op een andere pagina op elke gewenste maat. Sinds v3.539.42 geeft optie 3 op een pagina zonder TrimBox u de CropBox, zoals de referentie beschrijft, in plaats van de MediaBox met al zijn slug
Twee eigenschappen van capture vormen de code. Capture is destructief: de gevangen pagina wordt uit het document verwijderd, en het document mag nooit naar nul pagina's zakken, dus voeg het eerste uitvoervel toe voordat u iets vangt. Capture werkt bovendien alleen binnen één document, dus trekt u eerst elke invoer samen in één document; de technieken voor PDF-bronnen collateren en interleaven in één keer zijn direct toepasbaar
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;
// Effectieve trimmaat van pagina 1 (deze layout veronderstelt een uniforme trim)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// Voeg het eerste vel toe en maak het op maat; NewPage selecteert de nieuwe pagina
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// Elke capture verwijdert pagina 1, dus de volgende bronpagina schuift 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;
// Alleen het vel is over: twee bijgesneden pagina's per vel, naast elkaar
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // zelfde maat als het huidige vel
// Default-origine: Top is de bovenrand, gemeten vanaf de onderkant
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
Een capture op basis van de trim knipt alles buiten de TrimBox af, en dat is precies wat u wilt voor een digitaal proefexemplaar of een cut-and-stack-layout. Voor een drukvel dat na het drukken wordt bijgesneden, capture met optie 2 zodat de bleed overleeft, en ruim de cellen op de bleedbreedte. Omdat capture de bronpagina's verwijdert, raken bookmarks en links die ernaar wezen hun doelwit kwijt, dus imputeer naar een apart uitvoerbestand in plaats van een document te bewerken waarvan u de navigatie nog nodig heeft; pagina's vervangen zonder bookmarks te breken behandelt die kant van pagina-chirurgie
Moet de bron intact blijven, dan neemt ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) dezelfde optiewaarden 0 tot 4 (geef Lib.SelectedDocument door voor het huidige document), laat de paginaboom van de bron onaangeroerd, normaliseert overgeërfde paginarotatie in de formmatrix, en geeft een handle terug die DrawCapturedPage accepteert. CapturePageEx draait /Rotate niet terug, dus gedraaide invoer heeft die stap eerst nodig, en paginarotatie afvlakken zonder pagina-boxen te breken toont wat er met elke box gebeurt als u dat doet. Eén waarschuwing voor invoer die production boxes op /Pages-nodes kan bevatten: het importpad resolveert zijn box via een eigen voorouder-opzoeking, los van de twee paden die in v3.539.44 op één lijn zijn gebracht, dus controleer eerst HasPageBox(4) op de bronpagina en geef optie 1 (CropBox) door als die 0 teruggeeft. Zo blijft het resultaat aan de spec vast in plaats van aan hoe het bestand toevallig was geschreven
Snelnaslag paginaboxen
- Effectieve CropBox: de eigen CropBox van de pagina, anders de dichtstbijzijnde overgeërfde CropBox, anders de effectieve MediaBox (ISO 32000-1 §14.11.2)
- Effectieve BleedBox, TrimBox en ArtBox: de eigen entry van de leafpagina, anders de effectieve CropBox
- Alleen
Resources,MediaBox,CropBoxenRotateerven van/Pages-nodes (§7.7.3.4, Table 30); production boxes op/Pages-nodes worden genegeerd 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 geen box, 1 de eigen box van de pagina (direct of indirect), 2 een overgeërfde MediaBox of CropBox (direct of indirect)CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox met MediaBox-fallback, 2 tot 4 BleedBox, TrimBox of ArtBox met CropBox-fallback- Upgrade naar v3.539.44 of later voor consistente defaults en erfenis over boxqueries en capture heen
Paginaboxen zijn de plek waar de stille defaults van PDF prepress-toleranties op fracties van een millimeter ontmoeten, en een library past die defaults of overal op dezelfde manier toe, of geeft u twee antwoorden op één vraag. De volledige box-, capture- en Form XObject-API is gedocumenteerd op de productpagina van de PDFlibPas PDF Library for Delphi