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
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
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
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