Article technique

Importer du SVG dans un PDF comme Form XObject réutilisable

HotPDF importe un fichier SVG dans un PDF chargé ou en cours de création sous forme de Form XObject réutilisable via ImportSVGFormXObject, qui retourne le nom de ressource du formulaire créé. Le placer est un appel distinct, DrawSVGFormXObject, si bien qu'un seul import peut être apposé sur de nombreuses pages à des tailles différentes sans dupliquer le moindre octet de l'illustration

Cette séparation compte plus qu'il n'y paraît. Un logo rastérisé à 300 DPI et déposé sur 400 pages représente 400 copies d'un bitmap. Le même logo en tant que Form XObject est un seul objet référencé 400 fois, il reste net à n'importe quel zoom, et il s'imprime à la résolution du périphérique plutôt qu'à la résolution jugée suffisante au moment de la génération

Schéma d'un import SVG unique créant un seul Form XObject PDF que DrawSVGFormXObject place sur de nombreuses pages à des tailles différentes, comparé à des centaines de copies bitmap rastérisées
Un seul import écrit l'illustration une fois sous forme de Form XObject avec ses propres ressources, et chaque placement est une petite invocation qui n'ajoute aucun octet dupliqué

Pourquoi un Form XObject plutôt que dessiner le SVG à chaque fois ?

Un Form XObject est le mécanisme propre au PDF pour le contenu réutilisable : un flux de contenu autonome doté de sa propre boîte englobante et de son propre dictionnaire de ressources, invoqué par son nom. Importer du SVG dans un tel objet procure trois propriétés qu'un dessin direct n'offre pas. L'illustration n'existe qu'une seule fois dans le fichier. Chaque placement se réduit à une petite matrice et une invocation. Et les formes restent vectorielles, si bien qu'un visualiseur à 800 % de zoom trace des courbes, pas des pixels

La subtilité réside dans les ressources. Les polices et les états graphiques d'un formulaire doivent résider dans le dictionnaire /Resources propre au formulaire, et non dans les ressources de la page qui se trouvait être active au moment de l'import. HotPDF écrit les polices Base 14 et les entrées ExtGState dont un formulaire a besoin directement dans le formulaire lui-même, car un formulaire qui emprunte un nom à sa première page hôte s'y affiche correctement mais produit un nom de ressource non résolu sur chaque autre page où il est placé par la suite

Deux inversions : une pour la géométrie, une pour les glyphes

Le système de coordonnées SVG croît vers le bas depuis le coin supérieur gauche ; un Form XObject PDF utilise l'espace utilisateur PDF, qui croît vers le haut depuis le coin inférieur gauche. La transformation du viewBox racine effectue une inversion de l'axe Y pour que la géométrie se positionne correctement. Le texte a ensuite besoin d'une seconde inversion, locale, car un glyphe dessiné dans un repère déjà inversé se retrouverait à l'envers

Schéma des deux inversions de l'axe Y entre les coordonnées SVG et PDF : la transformation du viewBox racine corrige la géométrie et les glyphes de texte nécessitent une seconde inversion locale pour rester droits
La transformation du viewBox racine inverse l'axe Y une fois pour que la géométrie se positionne dans l'espace PDF, et le texte subit une seconde inversion, locale, sans quoi chaque glyphe arriverait à l'envers

C'est la même correction qui apparaît en sens inverse lorsqu'une page PDF est exportée en SVG, décrite dans l'export de pages PDF vers SVG. Une erreur à ce niveau se repère facilement sur une illustration contenant du texte et reste presque invisible sur une illustration qui n'en contient pas, ce qui explique pourquoi un logo accompagné d'un slogan constitue un meilleur test de fumée qu'un logo seul

uses
  HPDFDoc;

var
  Pdf: THotPDF;
  SvgBytes: TBytes;
  FormName: AnsiString;
  Stats: THPDFSVGImportStatistics;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalogue.pdf';
    Pdf.BeginDoc;

    SvgBytes := LoadFileBytes('brand-logo.svg');
    FormName := Pdf.ImportSVGFormXObject(SvgBytes, Stats);
    if FormName = '' then
      raise Exception.Create('SVG rejected - see the import statistics');

    // Un seul import, plusieurs placements : les octets de l'illustration n'existent qu'une fois
    for I := 1 to 40 do
    begin
      if I > 1 then
        Pdf.AddPage;
      Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 780, 120, 40);
    end;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Comment les hyperliens survivent-ils au trajet ?

Ils ne peuvent pas survivre à l'intérieur du formulaire, car un Form XObject est du contenu, tandis que les cibles de lien PDF sont des annotations, qui appartiennent à une page. Un élément <a> du SVG source ne peut donc pas être simplement copié dans le flux de contenu du formulaire

HotPDF résout ce problème en conservant les rectangles de lien comme métadonnées dans l'espace du formulaire pendant l'analyse, puis en les matérialisant sous forme d'annotations de lien au niveau de la page à chaque placement. DrawSVGFormXObject transforme ces rectangles à travers le rectangle cible que vous fournissez, si bien qu'un formulaire placé deux fois à des tailles différentes obtient deux jeux de zones cliquables correctement dimensionnés. Les rectangles de lien ne sont transformés qu'à travers la taille racine et n'entrent jamais dans le flux de contenu, ce qui garantit que l'illustration dessinée reste identique quel que soit le nombre de liens qu'elle porte. Passez AddLinks = False lorsque vous voulez l'illustration sans l'interactivité, par exemple sur une page destinée uniquement à l'impression

Ce que l'importateur refuse, et pourquoi c'est voulu

Le SVG est un dialecte XML doté de scripts, de références externes et d'expansion d'entités, ce qui fait d'un fichier SVG arbitraire une entrée potentiellement hostile, au même titre qu'un fichier HTML arbitraire. Le profil d'import est en échec fermé (fail-closed) pour tout ce qui sort du document : références vers des ressources externes, scripts, attributs de gestionnaires d'événements, DTD et tout url() qui n'est pas une référence de fragment local

Échec fermé signifie que l'import échoue plutôt que d'ignorer silencieusement la construction fautive. Cette distinction compte pour un pipeline en lot, car un rejet silencieux produit un document auquel il manque quelque chose que personne ne remarque, alors qu'un refus produit un fichier que l'on peut mettre en quarantaine et inspecter. Si vous acceptez des téléversements SVG venant d'utilisateurs, c'est exactement le comportement que vous voulez, et il se combine avec l'isolation au niveau processus décrite dans l'isolation de processus des codecs pour les images non fiables

Schéma d'un profil d'import SVG en échec fermé qui accepte les fonctionnalités vectorielles locales et refuse scripts, références externes et DTD en faisant échouer l'import plutôt que de les ignorer silencieusement
Le profil d'import refuse scripts, références externes et DTD en échouant complètement, ce qui vous donne un fichier à mettre en quarantaine plutôt qu'un document silencieusement incomplet

Une frontière interne mérite d'être connue si vous affichez également des polices couleur. Le sous-ensemble SVG strict utilisé pour les glyphes couleur OpenType SVG passe par un point d'entrée distinct et conserve son comportement d'échec d'origine, plus restrictif. L'import général d'illustrations emprunte un chemin différent, si bien qu'ajouter la prise en charge des arcs, de la transparence et de CSS à l'illustration importée n'a pas élargi ce qu'un glyphe de police est autorisé à contenir

Dégradés, masques et la règle du zéro rastérisation

Les dégradés sont importés sous forme de bandes de couleur vectorielles bornées, contraintes par le chemin de détourage de la géométrie d'origine, et non comme une image rastérisée. Les masques suivent la même approche : le contour du masque devient un chemin de détourage vectoriel. Le résultat est qu'une illustration riche en dégradés reste indépendante de la résolution et reste légère, là où un importateur qui rastérise produirait un gros objet image dont la qualité serait figée dès l'import

Le compromis se joue sur la fidélité dans les cas extrêmes. Une approximation en bandes d'un dégradé lisse n'est pas mathématiquement identique au dégradé SVG, et une illustration comportant des dizaines de masques doux superposés ressemblera davantage à l'original si vous la rendez plutôt sous forme d'image haute résolution. Pour les modèles de dégradé que le PDF prend en charge nativement, et pour savoir quand y recourir directement, voir le rendu des dégradés axiaux et radiaux

// Lire les statistiques d'import indique ce qui est effectivement passé
Writeln(Format('nodes=%d css rules=%d gradients=%d masks=%d',
  [Stats.NodeCount, Stats.CSSRuleCount, Stats.GradientCount,
   Stats.MaskCount]));
Writeln(Format('text runs=%d links=%d form bytes=%d',
  [Stats.TextRunCount, Stats.LinkCount, Stats.OutputBytes]));

// Placer le même formulaire à deux tailles produit deux jeux de liens corrects
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// Placement impression uniquement : illustration sans les zones cliquables
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

La place de l'import SVG parmi les autres voies vectorielles

Trois voies d'import vectoriel existent et elles ne sont pas interchangeables. L'import SVG convient aux illustrations produites par des outils de conception et à tout ce qui provient d'un pipeline web. L'import de métafichiers, traité dans l'import vectoriel EMF et WMF, convient au contenu généré par des applications Windows et par des composants de graphiques qui dessinent vers un contexte de périphérique. Le dessin direct via l'API de page convient aux illustrations que votre propre code calcule, sans aucun format intermédiaire

L'argument de la réutilisabilité favorise spécifiquement l'import SVG pour les éléments répétés. En-têtes de lettre, logos, filigranes, marques de certification et filets décoratifs sont tous dessinés à l'identique sur de nombreuses pages, et chacun d'eux se réduit à un objet formulaire plus une invocation par page. Mesurez l'écart de taille de fichier sur un document réel avant de décider que cela n'a pas d'importance : sur un catalogue de 500 pages avec un logo vectoriel par page, l'écart entre un seul formulaire et 500 copies en ligne représente généralement plusieurs mégaoctets

L'import SVG, l'import de métafichiers et le dessin vectoriel direct partagent le même générateur de flux de contenu pour Delphi et C++Builder ; la liste complète des fonctionnalités se trouve sur la page du composant PDF HotPDF Delphi