Quand une page PDF n’a pas de TrimBox, son TrimBox effectif est le CropBox de la page, et quand le CropBox manque aussi, c’est le MediaBox. BleedBox et ArtBox suivent la même règle. PDFlibPas, la PDF Library for Delphi, applique cette chaîne de défauts de façon cohérente dans GetPageBox, HasPageBox et CapturePageEx depuis v3.539.44, et il ignore les boxes de production posées sur un nœud /Pages, parce que ISO 32000-1 ne les laisse pas hériter
Cela ressemble à une note de bas de page, jusqu’à ce que vous imposiez un travail. Imaginez un intérieur de livre avec un MediaBox de 6,25 × 9,25 pouces, un CropBox réglé sur le rogne de 6 × 9 pouces, et pas de TrimBox, parce que celui qui l’a exporté n’a jamais pensé à en écrire un. Demandez la boîte de rogne, recevez la boîte média à la place, et chaque cellule de votre forme de presse traîne un huitième de pouce de fond perdu et de recettes dans sa voisine. PDFlibPas avait des défauts exactement dans cette zone, corrigés en v3.539.42 et v3.539.44, et la façon de les corriger en dit long sur la façon dont la sémantique des boxes de page devrait être implémentée dans n’importe quelle bibliothèque PDF
Quelle box s’applique quand une page n’a pas de TrimBox ?
La réponse est une chaîne de défauts fixée par ISO 32000-1 §14.11.2 : le CropBox prend le MediaBox par défaut, et la BleedBox, la TrimBox et l’ArtBox prennent chacune le CropBox par défaut. Rien d’autre que le CropBox ne prend le MediaBox directement par défaut. Une page qui ne définit qu’un MediaBox a donc cinq boxes identiques, et une page qui définit un MediaBox plus un CropBox a quatre boxes égales au CropBox
| Box | PDFlibPas BoxType | Défaut si absente | Héritable depuis /Pages |
|---|---|---|---|
| MediaBox | 1 | Aucun, l’entrée est requise | Oui |
| CropBox | 2 | MediaBox | Oui |
| BleedBox | 3 | CropBox | Non |
| TrimBox | 4 | CropBox | Non |
| ArtBox | 5 | CropBox | Non |
La chaîne en deux temps compte parce que le CropBox peut lui-même être hérité. Le TrimBox effectif d’une page qui n’a ni TrimBox ni CropBox propre est le CropBox de l’ancêtre le plus proche qui en a un, et à défaut, le MediaBox hérité. La spec ajoute une règle de plus, facile à oublier : les boxes crop, bleed, trim et art ne devraient pas dépasser la media box, et si c’est le cas, elles sont effectivement réduites à leur intersection avec elle. PDFlibPas rapporte chaque box telle que stockée dans le fichier, donc un validateur qui traite des entrées non fiables devrait écrêter contre le MediaBox lui-même
Quels attributs de page un nœud /Pages peut-il transmettre ?
Exactement quatre : Resources, MediaBox, CropBox et Rotate. ISO 32000-1 §7.7.3.4 définit l’héritage d’attributs, et la Table 30 ne marque que ces quatre entrées d’objet page comme héritables. BleedBox, TrimBox et ArtBox appartiennent à la page feuille. Un TrimBox écrit dans un nœud /Pages n’est pas une valeur héritée ; c’est une clé non standard qu’un lecteur conforme ignore
De tels fichiers non standard existent, typiquement avec un unique TrimBox sur le nœud racine de l’arbre de pages en raccourci de « chaque page a ce rogne ». Le raccourci paraît bon dans tout outil qui remonte /Parent pour chaque clé, et c’est là le problème : le fichier veut maintenant dire deux choses selon qui le lit. Un lecteur qui suit la spec ne voit pas de TrimBox et utilise le CropBox, tandis qu’un lecteur qui hérite tout voit la valeur du parent. Dans un pipeline de prépresse, cette ambiguïté finit sur la forme de presse
Les workflows PDF/X (ISO 15930) dépendent du TrimBox pour le format fini, et les profils PDF/X exigent que chaque page déclare un TrimBox ou un ArtBox. Une box parquée sur un nœud /Pages ne satisfait pas cette exigence, parce que la clé n’atteint jamais l’objet page. Le preflight devrait signaler de tels fichiers plutôt que les lire tranquillement dans un sens ou dans l’autre
Qu’est-ce que PDFlibPas faisait de travers avant v3.539.44 ?
PDFlibPas avait trois défauts distincts, tous dans l’écart entre ce que dit la spec et ce que faisaient deux chemins de code indépendants. Le premier a été corrigé en v3.539.42, les deux autres en v3.539.44
Les boxes de production prenaient le MediaBox par défaut à la capture
Avant v3.539.42, la routine interne qui prépare une page à la capture (elle copie les entrées héritées sur la page et comble les boxes manquantes) donnait à la BleedBox, à la TrimBox et à l’ArtBox les valeurs du MediaBox quand elles étaient absentes. CapturePageEx avec les options 2 à 4 lit son rectangle englobant dans exactement ces entrées comblées, donc sur une page qui ne définit qu’un CropBox, demander la boîte de rogne capturait toute la media box. GetPageBox appliquait déjà le défaut CropBox, et la référence de CapturePageEx disait depuis toujours que la crop box est utilisée quand la box demandée manque ; le code de capture était en désaccord avec les deux. Depuis v3.539.42, les trois boxes de production prennent le CropBox de la page par défaut, qui à ce stade est déjà sur la page (le sien, copié d’un ancêtre, ou comblé depuis le MediaBox), et seul le CropBox lui-même retombe sur le MediaBox
Deux chemins d’héritage, une règle sémantique
Le second défaut était l’héritage non standard lui-même, et la partie subtile était que PDFlibPas résolvait les boxes le long de deux chemins indépendants. Les requêtes de box (GetPageBox et HasPageBox) remontaient la chaîne /Parent par un helper, et la capture la remontait par un helper local séparé. Les deux héritaient de chaque clé, boxes de production comprises. N’en corriger qu’un aurait produit une contradiction à l’intérieur d’un même document : avec un TrimBox de 180 points de large sur le nœud /Pages et un CropBox de 380 points de large sur la page, GetPageBox aurait continué de rapporter un rogne de 180 pendant que CapturePageEx construisait un formulaire de 380. En v3.539.44, les deux chemins restreignent la marche /Parent aux quatre clés héritables, les boxes de production se lisent dans la feuille seule, et l’entrée parente égarée reste dans le fichier intacte, ni supprimée ni réécrite
HasPageBox ratait les tableaux parent directs
HasPageBox renvoie 0 quand la page n’a pas de box du type demandé, 1 quand la page a sa propre box (stockée directement ou via une référence indirecte), et 2 quand un MediaBox ou un CropBox est hérité d’un ancêtre. L’ancien code renvoyait 2 seulement quand la valeur héritée était une référence indirecte, si bien qu’un tableau direct hérité renvoyait 0. La correction sépare le déréférencement du test de tableau, et les deux représentations renvoient désormais 2. Depuis v3.539.44, HasPageBox pour une BleedBox, une TrimBox ou une ArtBox ne peut renvoyer que 0 ou 1
La leçon se généralise bien au-delà des boxes de page. Quand un morceau de sémantique de spec a deux points d’entrée d’implémentation dans une bibliothèque, corrigez-les ensemble et testez-les comme une matrice plutôt qu’avec un seul fichier du cas idéal. Le jeu de régression de PDFlibPas croise deux représentations de box parente (tableau direct et indirect) avec trois états de feuille (absente, tableau direct, tableau indirect) et trois options de capture (bleed, trim, art), soit 18 scénarios, et chacun vérifie le résultat de la requête, les bornes capturées, l’héritage légitime de MediaBox et CropBox, et l’entrée parente intacte
Comment lire le TrimBox effectif en Delphi ?
Appelez GetPageBox(4, Dimension) sur la page sélectionnée. PDFlibPas applique la chaîne de défauts pour vous, donc le résultat est le TrimBox effectif que la page en ait un ou non. Accouplez-le à HasPageBox quand vous devez savoir d’où vient la valeur, ce que fait d’habitude un rapport de preflight
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 = propre, 2 = hérité
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 et SetPageBox travaillent tous deux dans les réglages de coordonnées courants du document. Les exemples ici tournent avec les défauts : origine 0 (en bas à gauche, comme l’espace utilisateur PDF) et le point comme unité de mesure, si bien que la dimension Top est le bord supérieur mesuré depuis le bas de la page. Après SetOrigin(1), les dimensions Top et Bottom se mesurent plutôt vers le bas depuis le haut de la page, et après SetMeasurementUnits(1) chaque valeur revient en millimètres. Largeur et hauteur ne dépendent pas de l’origine
Trouver les boxes de production échouées sur des nœuds /Pages
Depuis v3.539.44, l’API de box ne voit plus un TrimBox sur un nœud /Pages, ce qui est correct, mais un outil de preflight veut d’ordinaire signaler un tel fichier plutôt que le lire en silence à la façon de la spec. Les nœuds de l’arbre de pages sont des objets ordinaires, donc l’API objet de bas niveau peut les trouver : parcourez les numéros d’objet jusqu’à GetMaxObjectNumber, lisez chacun avec GetObjectToString, et cherchez un dictionnaire /Pages qui porte une clé de box de production. La seconde moitié du contrôle est le test par page auquel PDF/X tient, et HasPageBox y répond désormais comme le ferait un validateur PDF/X, parce qu’un TrimBox parent ne compte plus
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. Boxes de production sur nœuds de l’arbre de pages : non standard, ignorées
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // les numéros libres ne renvoient pas de texte
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 : chaque page a besoin de son propre TrimBox ou 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. Réparation optionnelle : un rogne 6 x 9 po dans une media box 6.25 x 9.25 po
// (points, origine bas-gauche : Left, Top, Width, Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
La correspondance de texte est un contrôle pragmatique, pas un parseur. Elle repose sur le fait que PDFlibPas sérialise chaque entrée de dictionnaire en une clé, une espace et une valeur, ce qui vaut pour les objets relus via GetObjectToString. L’étape de réparation mérite une décision plutôt qu’un réflexe : la valeur parente égarée est peut-être bien ce que l’auteur voulait, mais confirmez-la contre le bon de commande avant de l’officialiser. SetPageBoxRange avec une plage vide applique la box à chaque page et renvoie le nombre de pages mises à jour. Quand la box existante d’une page est un tableau indirect, qu’une autre page ou un nœud /Pages peut partager, SetPageBox donne à cette page un nouveau tableau direct au lieu de réécrire l’objet partagé. Poser une BleedBox, une TrimBox ou une ArtBox fait aussi passer un document non verrouillé en PDF 1.3, la version qui a introduit ces entrées
Imposer des pages sur le TrimBox avec CapturePageEx
CapturePageEx(Page, 3) transforme une page en Form XObject dont la boîte englobante est le TrimBox effectif de la page, et DrawCapturedPage place ce formulaire sur une autre page à n’importe quelle taille. Depuis v3.539.42, l’option 3 sur une page sans TrimBox vous donne le CropBox, comme la référence le décrit, au lieu du MediaBox avec toute sa zone morte
Deux propriétés de la capture façonnent le code. La capture est destructive : la page capturée est retirée du document, et le document ne peut jamais tomber à zéro pages, donc ajoutez la première feuille de sortie avant de capturer quoi que ce soit. La capture ne marche aussi qu’à l’intérieur d’un seul document, donc ramenez d’abord chaque entrée dans un document unique ; les techniques de collationnement et d’entrelacement de sources PDF en une passe s’appliquent directement
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;
// Taille de rogne effective de la page 1 (ce layout suppose un rogne uniforme)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// Ajoutez et dimensionnez la première feuille ; NewPage sélectionne la nouvelle page
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// Chaque capture retire la page 1, donc la page source suivante remonte
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;
// Ne reste que la feuille : deux pages rognées par feuille, côte à côte
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // même taille que la feuille courante
// Origine par défaut : Top est le bord supérieur, mesuré depuis le bas
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
Une capture basée sur le rogne coupe tout ce qui est hors du TrimBox, ce qu’on veut pour une épreuve numérique ou un layout cut-and-stack. Pour une forme de presse rognée après impression, capturez avec l’option 2 pour que le fond perdu survive, et espacez les cellules de la largeur du fond perdu. Comme la capture retire les pages sources, les signets et liens qui pointaient vers eux perdent leurs cibles, donc imposez vers un fichier de sortie séparé plutôt que d’éditer un document dont vous avez encore besoin pour la navigation ; remplacer des pages sans casser les signets couvre ce côté de la chirurgie de pages
Quand la source doit rester intacte, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) prend les mêmes valeurs d’option de 0 à 4 (passez Lib.SelectedDocument pour le document courant), laisse l’arbre de pages source inchangé, normalise la rotation de page héritée dans la matrice du formulaire, et renvoie un handle que DrawCapturedPage accepte. CapturePageEx n’annule pas /Rotate, donc une entrée tournée a besoin de cette étape d’abord, et aplatir la rotation de page sans casser les boxes de page montre ce que devient chaque box quand vous le faites. Une précaution pour les entrées qui peuvent porter des boxes de production sur des nœuds /Pages : le chemin d’import résout sa box par sa propre remontée d’ancêtres, séparée des deux chemins alignés en v3.539.44, donc vérifiez d’abord HasPageBox(4) sur la page source et passez l’option 1 (CropBox) quand elle renvoie 0. Ça garde le résultat lié à la spec plutôt qu’à la façon dont le fichier s’est trouvé écrit
Aide-mémoire des boxes de page
- CropBox effectif : le CropBox propre de la page, sinon le CropBox hérité le plus proche, sinon le MediaBox effectif (ISO 32000-1 §14.11.2)
- BleedBox, TrimBox et ArtBox effectives : l’entrée propre de la page feuille, sinon le CropBox effectif
- Seuls
Resources,MediaBox,CropBoxetRotatehéritent des nœuds/Pages(§7.7.3.4, Table 30) ; les boxes de production sur des nœuds/Pagessont ignorées 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 pas de box, 1 la box propre de la page (directe ou indirecte), 2 un MediaBox ou CropBox hérité (direct ou indirect)CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox avec repli MediaBox, 2 à 4 BleedBox, TrimBox ou ArtBox avec repli CropBox- Passez à v3.539.44 ou ultérieure pour des défauts et un héritage cohérents entre requêtes de box et capture
Les boxes de page sont l’endroit où les défauts discrets de PDF rencontrent les tolérances de prépresse mesurées en fractions de millimètre, et une bibliothèque applique ces défauts de la même façon partout ou vous remet deux réponses à une question. L’API complète des boxes, de la capture et des Form XObject est documentée sur la page produit PDFlibPas PDF Library for Delphi