Article technique

HotPDF Resolution en Delphi : unités de dessin et UserWidth

Dans HotPDF Component, THotPDF.Resolution définit l'unité de dessin : chaque coordonnée X et Y, chaque marge, la taille passée à SetFont, et les résultats de TextWidth et GetWideTextWidth se mesurent en 1/Resolution de pouce. THPDFPage.Width et Height ne le suivent pas et restent en points, donc les bornes de mise en page doivent venir du UserWidth et du UserHeight en lecture seule. La raison habituelle de toucher à Resolution est un portage : un moteur de rapports qui pense déjà en 1/96 ou 1/144 de pouce se déplace plus facilement quand le côté PDF parle la même unité que quand chaque site d'appel reçoit un facteur de conversion. Ça marche bien, tant que vous savez quels nombres ont basculé vers la nouvelle unité et lesquels sont restés derrière

Que change réellement THotPDF.Resolution ?

THotPDF.Resolution ne change que la façon dont HotPDF lit les nombres que vous passez ; le PDF qu'il écrit est le même. Le setter fait deux lignes : SetResolution range la valeur et pose DocScale := Value / 72. À partir de là, XProjection et YProjection divisent chaque coordonnée par DocScale en route vers le flux de contenu, et SetFont divise la taille pareil avant de l'enregistrer. L'espace utilisateur PDF vaut par défaut 1/72 de pouce (ISO 32000-1 §8.3.2.3), donc à la Resolution par défaut de 72 la projection est l'identité et à 144 une unité de dessin vaut un demi-point. Aucune entrée /UserUnit n'est écrite. Cet attribut de page, ajouté en PDF 1.6, est une chose distincte que HotPDF expose comme THPDFPage.SetUserUnit. Un détail qui prend de court les gens venus des tutoriels TextOut : les coordonnées de page courent depuis le coin supérieur gauche avec Y croissant vers le bas, parce que YProjection calcule le haut de la MediaBox moins le Y mis à l'échelle, et ça reste vrai à toute Resolution

Comment THotPDF.Resolution définit l'unité de dessin en Delphi : le setter range DocScale comme Resolution divisée par 72, puis XProjection, YProjection et SetFont divisent chaque coordonnée et taille en route vers le flux de contenu, si bien que Resolution 72 est un mapping identité et Resolution 144 fait d'une unité de dessin un demi-point tandis que la page court toujours du haut gauche avec Y vers le bas
Rien ne bouge dans le fichier de sortie — seul le sens des nombres que vous passez change, voilà pourquoi le même flux de contenu apparaît à 72 et à 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 unité de dessin = 1/144 de pouce
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4 : Width = 595, UserWidth = 1190
    Margin := 144;                       // un pouce en unités de dessin
    Page.SetFont('Arial', [fsBold], 28); // 28/144 de pouce, une police de 14 pt
    Title := 'INVOICE 2026-0417';
    // Aligné à droite contre le bord de page mesuré dans la même unité
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // filet de 1 pt
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Pourquoi Page.Width contredit-il mes coordonnées à Resolution 144 ?

THPDFPage.Width et Height rapportent la page en points quelle que soit la Resolution du document, tandis que vos coordonnées sont en 1/Resolution de pouce, donc à 144 la page semble deux fois moins large qu'elle ne l'est. Une page A4 lit Width = 595 et Height = 842 à Resolution 72 et lit toujours 595 et 842 à 144, là où le bord droit se trouve en réalité à X = 1190. UserWidth et UserHeight, ajoutés en v2.766.0, renvoient Width * DocScale, c'est-à-dire la taille de page dans l'unité où vous dessinez. Avant leur existence, la bibliothèque mélangeait les deux en interne, et les symptômes à Resolution 144 étaient spectaculaires : les paragraphes passaient à la ligne après chaque caractère, THPDFTable.Render poussait chaque rangée sur une nouvelle page, et l'importateur HTML comme l'aplanisseur XFA dessinaient leur contenu à moitié taille, le formulaire aplati entassé dans le coin supérieur gauche. La mise en page des paragraphes, le rendu des tableaux, l'import HTML, le centrage EMF, le clip de page WMF et les diagnostics de mise en page lisent maintenant tous la taille en unités utilisateur. Votre propre code de mise en page devrait en faire autant : tout ce qui se compare à une coordonnée de dessin (une marge droite, un test de saut de page, un calcul de centrage) appartient à UserWidth et UserHeight, jamais à Width et Height

Piège un : assigner Width ou Height bascule la page en points

Régler Page.Width ou Page.Height fait passer la page en UserDefined en silence, et une page UserDefined ignore DocScale entièrement, donc tout ce que vous dessinez dessus ensuite est en points, pas en 1/Resolution de pouce. Le setter est ancien et prend des points à dessein, voilà pourquoi son sens a été laissé tel quel. La projection d'une page UserDefined est un simple X + MinX, et SetFont range la taille inchangée. À Resolution 144, le résultat est une page dont le contenu sort soudain deux fois plus grand que celui de la page précédente. La bibliothèque a fait exactement cette erreur elle-même : les pages de continuation de paragraphe copiaient la taille de la page précédente via Width, et chaque page de débordement basculait en points. Ces pages copient maintenant Size, Orientation et la Resolution de la page, et ne retombent sur Width et Height que si la page d'origine était déjà UserDefined

Deux sorties, selon ce dont vous avez besoin. Si une feuille standard fera l'affaire, réglez Page.Size et Page.Orientation et continuez à dessiner dans votre unité de Resolution. Si vous avez vraiment besoin d'une taille de page personnalisée, acceptez que ce soit une page en points et dessinez en points ; UserWidth égale Width là-bas, donc un code de mise en page qui lit toujours UserWidth continue de marcher sur les deux sortes de pages. Le test unitaire épingle ça : à Resolution 144 une page A4 rapporte un UserWidth de 1190, mais après Width := 500 et Height := 400 elle rapporte 500 et 400. Les pages chargées se comportent pareil, parce qu'une page reconstruite depuis un PDF existant ne connaît que sa MediaBox en points et dessine en points. Les pages que ce document a créées gardent leurs propres unités quand vous partez puis revenez via CurrentPageNumber, et c'est le cas depuis la v2.766.26

Pourquoi Page.Width contredit vos coordonnées à Resolution 144 dans HotPDF : Width et Height restent en points tandis que le dessin emploie le 1/144 de pouce, si bien qu'une page A4 lit 595 mais son bord droit loge à UserWidth 1190, et assigner Width bascule la page en UserDefined, qui ignore DocScale, donc les paragraphes passent à la ligne par caractère, les tableaux cassent par rangée et les tailles SetFont sont divisées par deux
Tout ce qui se compare à une coordonnée de dessin appartient à UserWidth et UserHeight — sur une page en points UserDefined les deux coïncident, donc le même code de mise en page survit aux deux

Piège deux : pourquoi les tailles de police sortent-elles à moitié taille ?

Une taille de police qui partait en points sort à moitié taille à Resolution 144 parce que SetFont traite son argument de taille comme des unités de dessin et le convertit en points avant de le ranger. En interne, SetFont range ASize / DocScale * DPI dans l'objet police courant, donc la valeur rangée est toujours en points. La bibliothèque a trébuché là-dessus deux fois : le repli de police dans WideTextOutBoxEx et la page de continuation de paragraphe remettaient tous deux cette valeur en points rangée à SetFont, qui la mettait à l'échelle une seconde fois et divisait le texte par deux. Votre code ne peut pas lire la taille rangée, mais le même bug réapparaît dès qu'une valeur en points venue d'ailleurs atteint SetFont : un TFont.Size d'une fiche VCL, une taille dans une définition de rapport, une longueur CSS pt. Convertissez d'abord, et incluez dans le facteur la Resolution propre de la page et le cas UserDefined, comme fait la lecture de metafiles quand elle rejoue le Canvas de la page (voir comment HotPDF importe les graphiques vectoriels EMF et WMF pour cette voie) :

// Unités de dessin par point sur la page courante. Reflète la projection
// de HotPDF : 1 sur une page dimensionnée via Width/Height, sinon
// (Resolution du document / 72) * (Resolution de la page / 72)
function UnitsPerPoint(Pdf: THotPDF): Single;
begin
  if Pdf.CurrentPage.Size = UserDefined then
    Result := 1
  else
    Result := (Pdf.Resolution / 72) * (Pdf.CurrentPage.Resolution / 72);
end;

procedure SetFontFromVcl(Pdf: THotPDF; Font: TFont);
begin
  // TFont.Size est en points ; SetFont attend des unités de dessin
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

La bibliothèque applique la même règle à ses propres constantes en points. La police de 12 points avec laquelle chaque nouvelle page démarre est maintenant multipliée par le facteur interne d'unités par point, donc elle fait 12 points à toute Resolution. DrawChart, dont les marges, tailles d'étiquettes et largeurs de filet sont toutes des points codés en dur, tourne maintenant avec l'échelle momentanément posée à 1. Ce qui reste en unités de dessin, à dessein, ce sont les défauts de paramètres publics comme la taille de module de DrawQRCode et la taille de police de tableau par défaut : ils font partie du contrat de l'API, donc à Resolution 144 ils valent la moitié de ce qu'ils valent à 72. Si vous dimensionnez vos rapports depuis un template, le guide de la sortie de rapports avec polices et images dans HotPDF couvre d'où viennent habituellement ces valeurs

Comment vérifier qu'une mise en page est indépendante de la Resolution ?

Le contrôle le plus fiable est une comparaison d'octets : rendez la même page à Resolution 72 puis à 144 avec chaque coordonnée et taille doublée, et les flux de contenu non compressés doivent être identiques. Les deux exécutions atterrissent sur les mêmes valeurs en points après projection, donc toute différence est une valeur qui a sauté la conversion. C'est comme ça que la suite de tests HotPDF contrôle paragraphes, tableaux, import HTML, aplanissement XFA, arcs, metafiles et images. La même technique marche pour votre propre code de rapport avec presque aucun harnais :

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // flux de contenu lisibles
    Pdf.FileName := FileName;
    Pdf.Resolution := Res;
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 10 * K);
    Pdf.CurrentPage.TextOut(36 * K, 36 * K, 0, 'Line 1');
    Pdf.CurrentPage.Rectangle(36 * K, 60 * K, 200 * K, 40 * K);
    Pdf.CurrentPage.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

// RenderPage('r72.pdf', 72, 1) et RenderPage('r144.pdf', 144, 2)
// doivent produire des flux de contenu de page identiques à l'octet près
Comment vérifier l'indépendance à la Resolution dans du code HotPDF Delphi : rendez la mise en page identique deux fois, une fois à Resolution 72 avec une échelle de 1 et une fois à 144 avec chaque coordonnée et taille de police doublée, puis exigez des flux de contenu non compressés identiques à l'octet près — un décalage pointe vers une page basculée en UserDefined via Width ou une valeur en points non convertie atteignant SetFont
Les deux exécutions atterrissent sur les mêmes valeurs en points après projection, donc toute différence est un nombre qui a sauté sa conversion — le même harnais dont dépend la suite de tests HotPDF

Contrôlez les opérateurs qui portent des nombres : Td, Tm, Tf, re, w et les tableaux TJ. Les octets au niveau fichier différeront encore sur la date de création et le /ID, donc comparez les flux, pas les fichiers entiers. Un décalage pointe presque toujours vers l'un des deux pièges ci-dessus : une page redimensionnée via Width, ou une valeur en points passée telle quelle dans SetFont. Si vous découvrez les appels de dessin eux-mêmes, commencez par le parcours TextOut de HotPDF pour taille, style et rotation, puis revenez et basculez la Resolution une fois que votre mise en page lit UserWidth. Les détails API complets et les téléchargements d'essai sont sur la page HotPDF Delphi PDF component