Article Technique

Page Stitching and Watermark Templates in Delphi PDFs

L'assemblage de pages compose le contenu de pages existantes sur un canevas partagé, ce qui est une opération différente de la fusion de fichiers. losLab PDF Library implémente l'assemblage de pages par Form XObject et des modèles de filigranes combinables pour Delphi : StitchPageOverlay, StitchPagesSideBySide et StitchPagesVertically placent les pages capturées à n'importe quelle échelle avec une fidélité vectorielle totale, et TPDFlibCompositeWatermark superpose des filigranes de texte et d'image sur une plage de pages en un seul appel. Si ce dont vous avez réellement besoin est d'ajouter des pages entières de plusieurs documents dans un seul fichier, cela relève de la famille de fusion (merge), traitée dans l'article sur la fusion rapide de PDF au niveau des octets ; cet article traite de la combinaison du contenu des pages dans un seul espace de coordonnées

Les demandes qui mènent ici sont courantes et exigeantes. Une école souhaite imposer deux pages d'examen sur une seule feuille paysage pour réduire de moitié la consommation de papier. Un relecteur de localisation souhaite afficher la page en anglais et sa traduction côte à côte sur une seule page, en les faisant défiler ensemble. Une équipe juridique souhaite que chaque projet soit marqué de trois couches à la fois : un logo dans le coin, la mention CONFIDENTIEL en diagonale au milieu, et une petite ligne de pied de page, uniquement sur les pages 1 à 3. Ces trois situations relèvent du même problème sous-jacent, à savoir placer un contenu de page déjà mis en page ailleurs et à une autre taille sans le dégrader sous forme d'image matricielle

Pourquoi la concaténation des flux de contenu PDF échoue-t-elle ?

L'assemblage naïf, qui consiste à copier le flux /Contents de la page source dans la page cible, échoue pour trois raisons structurelles. Premièrement, les coordonnées entrent en conflit : les deux flux de contenu supposent que leur origine se trouve en bas à gauche de leur propre MediaBox, de sorte que le contenu collé se superpose au contenu existant au mauvais endroit. Deuxièmement, les noms de ressources entrent en conflit : chaque page résout les opérateurs comme /F1 Tf et /Im0 Do par rapport à son propre dictionnaire /Resources, et deux pages lient régulièrement le même nom à des polices ou des images différentes, ce qui fait que l'une d'elles s'affiche silencieusement avec la mauvaise ressource. Troisièmement, une copie de flux brut ne permet aucun redimensionnement ; le mieux que vous puissiez faire est une translation

Les Form XObjects résolvent ces trois problèmes à la fois, et c'est pourquoi losLab PDF Library y base son assemblage. La norme ISO 32000-1 §8.10 définit un Form XObject comme un flux de contenu autonome avec son propre dictionnaire /Resources et son propre espace de coordonnées, placé dans une page en invoquant son nom sous n'importe quelle matrice de transformation active. CapturePage enveloppe une page existante entière, contenu et ressources compris, dans un XObject /Subtype /Form, et les fonctions d'assemblage le positionnent ensuite avec une matrice cm : mise à l'échelle, translation, rotation, à volonté. La source restant vectorielle, le texte reste du texte et les tracés restent des tracés à n'importe quel niveau de zoom, la même propriété qui rend les graphismes vectoriels et les motifs de dégradé indépendants de la résolution. Rien n'est rastérisé, et le dictionnaire de ressources isolé empêche la page capturée d'entrer en conflit avec la page cible pour le nom /F1

Comment combiner deux pages PDF sur une seule page ?

StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) est la réponse directe : elle capture les deux pages sources, crée une nouvelle page plus large et place les deux captures à gauche et à droite avec une gouttière entre elles, chacune mise à l'échelle pour s'adapter à TargetHeight. La fonction renvoie le nouveau numéro de page (basé sur 1) ou 0 en cas d'échec, et les deux pages sources sont capturées et masquées dans le cadre de l'opération. Le cas de l'examen à deux pages par feuille est réglé en trois lignes

var
  Lib: TPDFlib;
  NewPage: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('exam.pdf', '');
    // Pages 1 and 2 on one new wider page with an 18pt gutter,
    // both scaled to a 595pt output height (A4 landscape)
    NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
    if NewPage > 0 then
      Lib.SaveToFile('exam-2up.pdf');
  finally
    Lib.Free;
  end;
end;

Pour la composition verticale, StitchPagesVertically(PageRanges, Gap, TargetWidth) empile toute une plage de pages sur une seule page haute de style affiche. PageRanges utilise la syntaxe de plage standard comme '1-3,5'. Chaque page source est mise à l'échelle pour que sa largeur corresponde à TargetWidth, de sorte que la contribution en hauteur de chaque page correspond à sa propre hauteur multipliée par son propre facteur d'échelle, et la hauteur de la page de sortie est la somme de celles-ci plus une gouttière (Gap) entre chaque paire. C'est le format idéal pour les documents de révision à défilement continu ou les longs reçus assemblés à partir de sources au format lettre

Comment superposer une page sur une autre ?

StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) dessine une page existante sur une autre page existante, plutôt que d'en créer une nouvelle. La page source est capturée en tant que Form XObject et dessinée sur la cible au rectangle indiqué, avec une opacité (Opacity) de 0 à 1 (1 étant opaque). La largeur et la hauteur sont des paramètres libres, le positionnement s'étire donc si votre rapport diffère de l'aspect d'origine de la page source ; passez des valeurs proportionnelles si vous souhaitez une vignette non déformée de la source. Une utilisation typique est un document dont le bloc d'approbation est géré sur sa propre page et apposé sur la couverture

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('report.pdf', '');
    // Page 5 holds the approval block; place it on page 1
    // in a 260 x 180 box at 45% opacity. Page 5 is consumed:
    // it is captured and hidden, so pages 6..n become 5..n-1
    if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
      Lib.SaveToFile('report-approved.pdf');
  finally
    Lib.Free;
  end;
end;

Qu'advient-il des numéros de page après une capture ?

CapturePage consomme la page source : son contenu est déplacé dans le XObject et la page désormais vide est masquée, de sorte que chaque page dont le numéro est supérieur à la page source descend immédiatement d'un rang. C'est la sémantique la plus importante de toute l'API d'assemblage. losLab PDF Library gère ce calcul à l'intérieur de chaque appel d'assemblage, StitchPageOverlay ajuste l'index de la cible lorsque la cible se situe après la source, et StitchPagesSideBySide capture en premier la page ayant le numéro le plus élevé pour que l'index de la seconde capture reste valide, mais votre propre code doit tenir compte de ce décalage entre les appels. Deux règles garantissent la correction du code multi-assemblage : enregistrez toutes les tailles de page ou numéros de page dont vous aurez besoin avant la première capture, car ils changeront après. Et lorsque vous capturez vous-même plusieurs pages dans une boucle, itérez dans l'ordre décroissant des pages, de sorte que masquer une page ultérieure ne perturbe jamais l'index d'une page antérieure que vous n'avez pas encore capturée

Comment appliquer un filigrane multicouche à une plage de pages ?

La couche de modèle de filigrane dans PDFlibWatermark.pas transforme le concept « définir une fois, appliquer à plusieurs pages » en objets. TPDFlibWatermarkBase contient ce que tous les filigranes partagent : une position Position en neuf points (de wpTopLeft à wpBottomRight, plus wpCustom qui utilise des coordonnées X/Y brutes), un angle Angle en degrés, une opacité Opacity de 0 à 1, et une chaîne de plage de pages PageRange où une valeur vide signifie toutes les pages. TPDFlibTextWatermark ajoute du texte, un identifiant de police (Helvetica par défaut), la taille et une couleur RGB ; TPDFlibImageWatermark ajoute un identifiant d'image provenant d'une image précédemment ajoutée ainsi qu'une largeur et une hauteur de placement. TPDFlibCompositeWatermark contient une liste de sous-filigranes et les applique de manière récursive, et TPDFlib.ApplyWatermark est le point d'entrée en un seul appel qui respecte la plage de pages propre au modèle et renvoie le nombre de pages marquées. Le marquage juridique à trois couches ressemble à ceci

var
  Lib: TPDFlib;
  Logo: TPDFlibImageWatermark;
  Diagonal, Footer: TPDFlibTextWatermark;
  Stack: TPDFlibCompositeWatermark;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('draft.pdf', '');
    Stack := TPDFlibCompositeWatermark.Create;
    try
      Logo := TPDFlibImageWatermark.Create;
      Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
      Logo.Position := wpTopRight;
      Logo.Width := 90;
      Logo.Height := 32;
      Stack.Add(Logo);

      Diagonal := TPDFlibTextWatermark.Create;
      Diagonal.Text := 'CONFIDENTIAL';
      Diagonal.Position := wpCenter;
      Diagonal.Angle := 45;
      Diagonal.FontSize := 60;
      Diagonal.Opacity := 0.15;
      Stack.Add(Diagonal);

      Footer := TPDFlibTextWatermark.Create;
      Footer.Text := 'Internal review copy';
      Footer.Position := wpBottomCenter;
      Footer.FontSize := 9;
      Footer.Opacity := 0.6;
      Stack.Add(Footer);

      Stack.PageRange := '1-3';   // children inherit this range
      Lib.ApplyWatermark(Stack);
      Lib.SaveToFile('draft-marked.pdf');
    finally
      Stack.Free;                 // composite frees its children
    end;
  finally
    Lib.Free;
  end;
end;

Deux détails de composition méritent d'être explicités. L'héritage de plage est unidirectionnel et non destructif : un enfant avec une plage PageRange vide hérite temporairement de la plage du composite lors de la méthode Apply, tandis qu'un enfant qui définit sa propre plage la conserve. Ainsi, vous pouvez épingler le pied de page à chaque page tandis que le filigrane diagonal ne couvre que la section du projet. De plus, la propriété suit le composite : TPDFlibCompositeWatermark.Destroy libère ses enfants, vous ne libérez donc que le composite et rien d'autre, comme dans l'exemple ci-dessus

Limites à connaître

Deux limites réelles s'appliquent, découlant du fonctionnement du mécanisme. Premièrement, une capture enveloppe uniquement le flux de contenu et les ressources de la page. Les annotations, liens, champs de formulaire et commentaires existent en tant qu'objets distincts dans le dictionnaire de la page, et non dans le flux de contenu, ils ne sont donc pas transférés dans le XObject ; une mise en page assemblée conserve le contenu visible de la page mais pas la couche interactive de ses sources. Deuxièmement, le positionnement du texte en neuf points est une approximation : sans un moteur de mise en page complet, la bibliothèque ne peut connaître la largeur exacte d'une chaîne à l'écran, elle estime donc la boîte de texte à 40% de la largeur de la page lors de la résolution de positions comme wpCenter. C'est suffisant pour centrer un texte court, mais pour un positionnement exact au pixel près de longues chaînes, passez à wpCustom et calculez vous-même les coordonnées

La mise à l'échelle n'est pas une contrainte pratique ici, puisque la capture et l'assemblage manipulent des références d'objets plutôt que de ré-encoder le contenu de la page ; si vos sources font des centaines de mégaoctets, la même méthode décrite dans le guide sur la fusion et la division de gros fichiers avec accès direct s'applique avant l'assemblage. Les fonctions d'assemblage et les classes de modèles de filigranes présentées ici sont fournies dans la version actuelle de losLab PDF Library pour Delphi, C# et VB.NET, aux côtés des primitives de niveau inférieur CapturePage, DrawCapturedPage et DrawCapturedPageMatrix lorsque vous avez besoin d'une mise en page non couverte par les trois appels d'assemblage prédéfinis