Când o pagină PDF nu are TrimBox, TrimBox-ul ei efectiv e CropBox-ul paginii, iar când lipsește și CropBox-ul, e MediaBox-ul. BleedBox și ArtBox urmează aceeași regulă. PDFlibPas, PDF Library for Delphi, aplică acest lanț implicit în mod consecvent în GetPageBox, HasPageBox și CapturePageEx din v3.539.44 și ignoră casetele de producție plasate pe un nod /Pages, pentru că ISO 32000-1 nu le lasă să moștenească
Asta sună a notă de subsol până când impuneți un job. Imaginați-vă un interior de carte cu un MediaBox de 6,25 × 9,25 in, un CropBox setat la trim-ul de 6 × 9 in și niciun TrimBox, pentru că cel care l-a exportat nu s-a gândit să scrie unul. Cereți caseta de trim, primiți caseta de media, iar fiecare celulă de pe forma de presă trage un optim de țol de bleed și slug în vecina ei. PDFlibPas a avut defecte exact în zona asta, reparate în v3.539.42 și v3.539.44, iar felul în care au fost reparate spune ceva despre cum ar trebui implementată semantica de page-box în orice bibliotecă PDF
Ce casetă se aplică când o pagină nu are TrimBox?
Răspunsul e un lanț implicit fix din ISO 32000-1 §14.11.2: CropBox-ul are implicit MediaBox-ul, iar BleedBox, TrimBox și ArtBox au implicit fiecare CropBox-ul. Nimic în afară de CropBox nu are implicit direct MediaBox-ul. O pagină care definește doar un MediaBox are deci cinci casete identice, iar o pagină care definește un MediaBox plus un CropBox are patru casete egale cu CropBox-ul
| Casetă | BoxType PDFlibPas | Implicită când lipsește | Moștenibilă de la /Pages |
|---|---|---|---|
| MediaBox | 1 | Niciuna, intrarea e obligatorie | Da |
| CropBox | 2 | MediaBox | Da |
| BleedBox | 3 | CropBox | Nu |
| TrimBox | 4 | CropBox | Nu |
| ArtBox | 5 | CropBox | Nu |
Lanțul în doi pași contează pentru că CropBox-ul poate fi el însuși moștenit. TrimBox-ul efectiv al unei pagini care nu are nici TrimBox, nici CropBox propriu e CropBox-ul celui mai apropiat ancestor care are unul, iar în lipsa lui, MediaBox-ul moștenit. Specificația adaugă încă o regulă ușor de uitat: casetele de crop, bleed, trim și art nu ar trebui să se extindă dincolo de media box, iar dacă o fac, sunt efectiv reduse la intersecția lor cu el. PDFlibPas raportează fiecare casetă așa cum e stocată în fișier, deci un validator care prelucrează input nesigur ar trebui să limiteze el însuși față de MediaBox
Ce atribute de pagină poate transmite în jos un nod /Pages?
Exact patru: Resources, MediaBox, CropBox și Rotate. ISO 32000-1 §7.7.3.4 definește moștenirea de atribute, iar Tabelul 30 marchează doar acele patru intrări de obiect pagină ca moștenibile. BleedBox, TrimBox și ArtBox aparțin paginii frunză. Un TrimBox scris într-un nod /Pages nu e o valoare moștenită; e o cheie non-standard pe care un cititor conform o ignoră
Fișiere non-standard ca acesta există, de obicei cu un singur TrimBox pe nodul rădăcină al arborelui de pagini, ca prescurtare pentru „fiecare pagină are acest trim”. Prescurtarea pare corectă în orice unealtă care parcurge /Parent pentru fiecare cheie, și tocmai asta e problema: fișierul înseamnă acum două lucruri în funcție de cine îl citește. Un cititor care urmează specificația nu vede niciun TrimBox și folosește CropBox-ul, în timp ce un cititor care moștenește tot vede valoarea părintelui. Într-un pipeline de prepress ambiguitatea aceasta ajunge pe forma de presă
Fluxurile de lucru PDF/X (ISO 15930) depind de TrimBox pentru mărimea finită, iar profilele PDF/X cer fiecărei pagini să declare un TrimBox sau un ArtBox. O casetă parcată pe un nod /Pages nu satisface cerința aceea, pentru că cheia nu ajunge niciodată în obiectul paginii. Preflight-ul ar trebui să semnalizeze asemenea fișiere în loc să le citească în tăcere într-un fel sau altul
Ce a greșit PDFlibPas înainte de v3.539.44?
PDFlibPas a avut trei defecte separate, toate în spațiul dintre ce spune specificația și ce făceau două căi de cod independente. Primul a fost reparat în v3.539.42, celelalte două în v3.539.44
Casetele de producție aveau implicit MediaBox-ul în timpul capturării
Înainte de v3.539.42, rutina internă care pregătește o pagină pentru capturare (ea copiază intrările moștenite pe pagină și completează casetele lipsă) le dădea BleedBox, TrimBox și ArtBox valorile MediaBox-ului când erau absente. CapturePageEx cu opțiunile 2 până la 4 își citește dreptunghiul de încadrare exact din intrările completate, deci pe o pagină care definește doar un CropBox, cererea casetei de trim captura tot media box-ul. GetPageBox aplicase deja implicitul CropBox, iar referința CapturePageEx spusese tot timpul că se folosește caseta de crop când caseta cerută lipsește; codul de capturare contrazicea ambele. Din v3.539.42 cele trei casete de producție au implicit CropBox-ul paginii, care la momentul acela e deja pe pagină (propriul, copiat de la un ancestor sau completat din MediaBox), iar doar CropBox-ul în sine cade înapoi pe MediaBox
Două căi de moștenire, o singură regulă semantică
Al doilea defect era moștenirea non-standard în sine, iar partea subtilă era că PDFlibPas rezolva casetele de-a lungul a două căi independente. Interogările de casete (GetPageBox și HasPageBox) parcurgeau lanțul /Parent printr-un helper, iar capturarea îl parcurgea printr-un helper local separat. Ambele moșteneau fiecare cheie, casetele de producție incluse. Repararea doar uneia ar fi produs o contradicție în interiorul unui singur document: cu un TrimBox de 180 de puncte lățime pe nodul /Pages și un CropBox de 380 de puncte lățime pe pagină, GetPageBox ar fi raportat în continuare o lățime de trim de 180, în timp ce CapturePageEx construia un form de 380 lățime. În v3.539.44 ambele căi restricționează parcurgerea /Parent la cele patru chei moștenibile, casetele de producție se citesc doar de pe frunză, iar intrarea rătăcită de la părinte rămâne în fișier neatinsă, nici ștersă, nici rescrisă
HasPageBox ratează tablourile directe de la părinte
HasPageBox întoarce 0 când pagina nu are nicio casetă de tipul cerut, 1 când pagina are propria casetă (stocată direct sau printr-o referință indirectă) și 2 când un MediaBox sau CropBox e moștenit de la un ancestor. Codul vechi întorcea 2 doar când valoarea moștenită era o referință indirectă, deci un tablou direct moștenit întorcea 0. Repararea separă dereferențierea de testul de tablou, iar ambele reprezentări întorc acum 2. Din v3.539.44, HasPageBox pentru un BleedBox, TrimBox sau ArtBox poate întoarce doar 0 sau 1
Lecția se generalizează mult dincolo de casetele de pagină. Când o bucată de semantică de specificație are două puncte de intrare de implementare într-o bibliotecă, reparați-le împreună și testați-le ca o matrice, nu cu un singur fișier de cale fericită. Setul de regresie PDFlibPas traversează două reprezentări de casetă părinte (tablou direct și indirect) cu trei stări de frunză (absentă, tablou direct, tablou indirect) și trei opțiuni de capturare (bleed, trim, art), dând 18 scenarii, iar fiecare verifică rezultatul interogării, marginile capturate, moștenirea legitimă de MediaBox și CropBox și intrarea părinte neatinsă
Cum citesc TrimBox-ul efectiv în Delphi?
Apelați GetPageBox(4, Dimension) pe pagina selectată. PDFlibPas aplică lanțul implicit în locul dvs., deci rezultatul e TrimBox-ul efectiv indiferent dacă pagina are unul sau nu. Împerecheați-l cu HasPageBox când trebuie să știți de unde vine valoarea, ceea ce un raport de preflight face de obicei
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 = propriu, 2 = moștenit
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.
Atât GetPageBox, cât și SetPageBox lucrează în setările de coordonate curente ale documentului. Exemplele de aici rulează cu implicitele: origine 0 (stânga-jos, ca în user space-ul PDF) și puncte ca unitate de măsură, deci dimensiunea Top e latura de sus măsurată în sus de la baza paginii. După SetOrigin(1), dimensiunile Top și Bottom se măsoară în jos de la partea de sus a paginii, iar după SetMeasurementUnits(1) fiecare valoare revine în milimetri. Lățimea și înălțimea nu depind de origine
Găsirea casetelor de producție rămase pe noduri /Pages
Din v3.539.44 API-ul de casete nu mai vede un TrimBox pe un nod /Pages, ceea ce e corect, dar o unealtă de preflight vrea de obicei să raporteze un asemenea fișier, nu să-l citească în tăcere pe calea specificației. Nodurile arborelui de pagini sunt obiecte obișnuite, deci API-ul de obiecte de nivel jos le poate găsi: parcurgeți numerele de obiect până la GetMaxObjectNumber, citiți fiecare cu GetObjectToString și căutați un dicționar /Pages care cară o cheie de casetă de producție. A doua jumătate a verificării e testul per pagină căruia îi pasă PDF/X, iar HasPageBox răspunde acum la el așa cum ar face-o un validator PDF/X, pentru că un TrimBox de la părinte nu mai contează
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. Casete de producție pe noduri de arbore de pagini: non-standard și ignorate
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // numerele libere nu întorc 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: fiecare pagină are nevoie de propriul TrimBox sau 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. Reparare opțională: un trim de 6 x 9 in într-un media box de 6.25 x 9.25 in
// (puncte, origine stânga-jos: Left, Top, Width, Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
Potrivirea de text e o verificare pragmatică, nu un parser. Ea se sprijină pe faptul că PDFlibPas serializează fiecare intrare de dicționar ca o cheie, un spațiu și o valoare, ceea ce se păstrează pentru obiectele citite înapoi prin GetObjectToString. Pasul de reparare merită o decizie, nu un reflex: valoarea rătăcită de la părinte poate fi chiar ceea ce a intenționat autorul, dar confirmați-o contra bonului de job înainte s-o oficializați. SetPageBoxRange cu un interval gol aplică caseta fiecărei pagini și întoarce numărul de pagini actualizate. Când caseta existentă a unei pagini e un tablou indirect, pe care altă pagină sau un nod /Pages îl poate partaja, SetPageBox îi dă paginii un tablou direct nou în loc să rescrie obiectul partajat. Setarea unui BleedBox, TrimBox sau ArtBox ridică și un document deblocat la PDF 1.3, versiunea care a introdus aceste intrări
Impunerea paginilor pe TrimBox cu CapturePageEx
CapturePageEx(Page, 3) transformă o pagină într-un Form XObject a cărui casetă de încadrare e TrimBox-ul efectiv al paginii, iar DrawCapturedPage plasează formul acela pe altă pagină la orice mărime. Din v3.539.42, opțiunea 3 pe o pagină fără TrimBox vă dă CropBox-ul, așa cum descrie referința, în locul MediaBox-ului cu tot slug-ul lui
Două proprietăți ale capturării modelează codul. Capturarea e distructivă: pagina capturată e eliminată din document, iar documentul nu poate coborî niciodată la zero pagini, deci adăugați prima foaie de ieșire înainte să capturați ceva. Capturarea lucrează și doar în interiorul unui singur document, deci aduceți toate input-urile într-un singur document întâi; tehnicile din colationarea și intercalarea surselor PDF într-o singură trecere se aplică direct
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;
// Mărimea efectivă de trim a paginii 1 (acest layout presupune un trim uniform)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// Adaugă și dimensionează prima foaie; NewPage selectează pagina nouă
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// Fiecare captură elimină pagina 1, deci următoarea pagină sursă urcă
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;
// A rămas doar foaia: două pagini trim-uite per foaie, una lângă alta
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // aceeași mărime ca foaia curentă
// Origine implicită: Top e latura de sus, măsurată de jos
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
O capturare pe baza trim-ului decupează tot ce e în afara TrimBox-ului, exact ce vreți pentru o probă digitală sau un layout cut-and-stack. Pentru o formă de presă tăiată după tipărire, capturați cu opțiunea 2, astfel încât bleed-ul să supraviețuiască, și spațiați celulele după lățimea de bleed. Pentru că capturarea elimină paginile sursă, bookmark-urile și linkurile care arătau către ele își pierd țintele, deci impuneți într-un fișier de ieșire separat în loc să editați un document a cărui navigare vă mai trebuie; înlocuirea paginilor fără a rupe bookmark-urile acoperă partea aceea a chirurgiei de pagini
Când sursa trebuie să rămână intactă, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) primește aceleași valori de opțiune de la 0 la 4 (pasați Lib.SelectedDocument pentru documentul curent), lasă arborele de pagini sursă neschimbat, normalizează rotația de pagină moștenită în matricea form-ului și întoarce un handle pe care DrawCapturedPage îl acceptă. CapturePageEx nu anulează /Rotate, deci input-ul rotit are nevoie de pasul acela mai întâi, iar aplanarea rotației de pagină fără a rupe casetele de pagină arată ce se întâmplă cu fiecare casetă când o faceți. O precauție pentru input-urile care pot căra casete de producție pe noduri /Pages: calea de import își rezolvă caseta prin propria căutare de ancestor, separată de cele două căi aliniate în v3.539.44, deci verificați HasPageBox(4) pe pagina sursă mai întâi și pasați opțiunea 1 (CropBox) când întoarce 0. Asta ține rezultatul legat de specificație, nu de felul în care fișierul s-a întâmplat să fie scris
Referință rapidă a casetelor de pagină
- CropBox efectiv: CropBox-ul propriu al paginii, altfel cel mai apropiat CropBox moștenit, altfel MediaBox-ul efectiv (ISO 32000-1 §14.11.2)
- BleedBox, TrimBox și ArtBox efective: intrarea proprie a paginii frunză, altfel CropBox-ul efectiv
- Doar
Resources,MediaBox,CropBoxșiRotatemoștenesc de la noduri/Pages(§7.7.3.4, Tabelul 30); casetele de producție pe noduri/Pagessunt ignorate 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 nicio casetă, 1 caseta proprie a paginii (directă sau indirectă), 2 un MediaBox sau CropBox moștenit (direct sau indirect)CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox cu fallback pe MediaBox, 2 până la 4 BleedBox, TrimBox sau ArtBox cu fallback pe CropBox- Faceți upgrade la v3.539.44 sau mai nou pentru implicite și moștenire consecvente între interogările de casete și capturare
Casetele de pagină sunt locul unde implicitele tăcute ale PDF-ului întâlnesc toleranțele de prepress măsurate în fracțiuni de milimetru, iar o bibliotecă ori aplică acele implicite la fel peste tot, ori vă pune în mână două răspunsuri la o singură întrebare. API-ul complet de casete, capturare și Form XObject e documentat pe pagina de produs PDFlibPas PDF Library for Delphi