In der HotPDF Component definiert THotPDF.Resolution die Zeicheneinheit: Jede X- und Y-Koordinate, jeder Rand, die an SetFont übergebene Größe und die Ergebnisse von TextWidth und GetWideTextWidth werden in 1/Resolution Zoll gemessen. THPDFPage.Width und Height folgen ihr nicht und bleiben in Punkt, Layoutgrenzen müssen also vom schreibgeschützten UserWidth und UserHeight kommen. Der übliche Grund, Resolution anzufassen, ist eine Portierung: Eine Report-Engine, die bereits in 1/96 oder 1/144 Zoll denkt, lässt sich leichter herüberziehen, wenn die PDF-Seite dieselbe Einheit spricht, als wenn jede Aufrufstelle einen Umrechnungsfaktor bekommt. Das funktioniert gut, solange Sie wissen, welche Zahlen mit in die neue Einheit gezogen sind und welche zurückgeblieben sind
Was ändert THotPDF.Resolution überhaupt?
THotPDF.Resolution ändert nur, wie HotPDF die Zahlen liest, die Sie hereinreichen; die PDF, die es schreibt, bleibt dieselbe. Der Setter besteht aus zwei Zeilen: SetResolution speichert den Wert und setzt DocScale := Value / 72. Von da an teilen XProjection und YProjection jede Koordinate auf dem Weg in den Content-Stream durch DocScale, und SetFont teilt die Größe auf dieselbe Weise, bevor er sie notiert. Der PDF User Space ist standardmäßig 1/72 Zoll (ISO 32000-1 §8.3.2.3), bei der Default-Resolution von 72 ist die Projektion also die Identität, und bei 144 ist eine Zeicheneinheit ein halber Punkt. Kein /UserUnit-Eintrag wird geschrieben. Jenes Seitenattribut, in PDF 1.6 hinzugekommen, ist ein eigenes Ding, das HotPDF als THPDFPage.SetUserUnit anbietet. Ein Detail, das Leute aus den TextOut-Tutorials erwischt: Seitenkoordinaten laufen von der oberen linken Ecke mit Y nach unten wachsend, weil YProjection die MediaBox-oberkante minus das skalierte Y rechnet — und das gilt bei jeder Resolution
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // 1 Zeicheneinheit = 1/144 Zoll
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // ein Zoll in Zeicheneinheiten
Page.SetFont('Arial', [fsBold], 28); // 28/144 Zoll, ein 14-pt-Font
Title := 'INVOICE 2026-0417';
// Rechtsbündig gegen die Seitenkante, gemessen in derselben Einheit
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // 1-pt-Linie
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Warum widerspricht Page.Width meinen Koordinaten bei Resolution 144?
THPDFPage.Width und Height melden die Seite in Punkt, egal welche Dokument-Resolution gilt, während Ihre Koordinaten in 1/Resolution Zoll sind — bei 144 sieht die Seite also halb so breit aus, wie sie ist. Eine A4-Seite liest Width = 595 und Height = 842 bei Resolution 72 und liest weiterhin 595 und 842 bei 144, wo die rechte Kante tatsächlich bei X = 1190 liegt. UserWidth und UserHeight, hinzugekommen in v2.766.0, liefern Width * DocScale, also die Seitengröße in der Einheit, in der Sie zeichnen. Bevor es sie gab, mischte die Bibliothek beide intern, und die Symptome bei Resolution 144 waren dramatisch: Absätze brachen nach jedem Zeichen um, THPDFTable.Render schob jede Zeile auf eine neue Seite, und sowohl der HTML-Importer als auch der XFA-Flattener zeichneten ihren Inhalt halb groß, das abgeflachte Formular quetschte sich in die obere linke Ecke. Absatzlayout, Tabellen-Rendering, HTML-Import, EMF-Zentrierung, der WMF-Seitenbeschnitt und Layout-Diagnostik lesen jetzt alle die User-Unit-Größe. Ihr eigener Layout-Code sollte es auch tun: Alles, was sich mit einer Zeichenkoordinate vergleicht (ein rechter Rand, ein Seitenumbruch-Test, eine Zentrierberechnung), gehört auf UserWidth und UserHeight, niemals auf Width und Height
Falle eins: Width oder Height zuweisen schaltet die Seite auf Punkt um
Setzt man Page.Width oder Page.Height, wechselt die Seite lautlos auf UserDefined, und eine UserDefined-Seite ignoriert DocScale komplett — alles, was Sie danach darauf zeichnen, ist in Punkt, nicht in 1/Resolution Zoll. Der Setter ist alt und nimmt bewusst Punkt entgegen, deshalb ließ man seine Bedeutung in Ruhe. Die Projektion einer UserDefined-Seite ist schlicht X + MinX, und SetFont speichert die Größe unverändert. Bei Resolution 144 ergibt das eine Seite, deren Inhalt plötzlich doppelt so groß herauskommt wie der der Seite davor. Die Bibliothek hat exakt diesen Fehler selbst gemacht: Absatz-Fortsetzungsseiten kopierten die Seitengröße des Vorgängers über Width, und jede Überlaufseite schaltete auf Punkt um. Diese Seiten kopieren jetzt Size, Orientation und die Seiten-Resolution und fallen nur dann auf Width und Height zurück, wenn die Originalseite bereits UserDefined war
Zwei Wege raus, je nachdem, was Sie brauchen. Reicht ein Standardblatt, setzen Sie Page.Size und Page.Orientation und zeichnen weiter in Ihrer Resolution-Einheit. Wirklich nötig ist eine eigene Seitengröße, dann akzeptieren Sie, dass es eine Punktseite ist, und zeichnen in Punkt; UserWidth entspricht dort Width, Layout-Code, der immer UserWidth liest, funktioniert also auf beiden Seitenarten. Der Unit-Test nagelt das fest: Bei Resolution 144 meldet eine A4-Seite ein UserWidth von 1190, nach Width := 500 und Height := 400 aber 500 und 400. Geladene Seiten benehmen sich genauso, denn eine aus einer bestehenden PDF wiederaufgebaute Seite kennt nur ihre MediaBox in Punkt und zeichnet in Punkt. Seiten, die dieses Dokument erzeugt hat, behalten ihre eigene Einheit, wenn Sie weg- und über CurrentPageNumber zurückschalten — so ist es seit v2.766.26
Falle zwei: Warum kommen Fontgrößen halb so groß heraus?
Eine Schriftgröße, die als Punkt begann, kommt bei Resolution 144 halb so groß heraus, weil SetFont sein Größenargument als Zeicheneinheit behandelt und es vor dem Speichern in Punkt umrechnet. Intern speichert SetFont ASize / DocScale * DPI im aktuellen Font-Objekt, der gespeicherte Wert ist also immer Punkt. Die Bibliothek ist zweimal darüber gestolpert: Der Font-Fallback in WideTextOutBoxEx und die Absatz-Fortsetzungsseite reichten beide diesen gespeicherten Punkt-Wert an SetFont zurück, das ihn ein zweites Mal skalierte und den Text halbierte. Ihr Code kann die gespeicherte Größe nicht lesen, aber derselbe Bug taucht immer dann auf, wenn ein Punkt-Wert von woanders zu SetFont gelangt: ein TFont.Size aus einem VCL-Formular, eine Größe aus einer Report-Definition, eine pt-Länge aus CSS. Wandeln Sie ihn erst um, und ziehen Sie die eigene Resolution der Seite und den UserDefined-Fall in den Faktor ein, wie das Metafile-Playback tut, wenn es das Seiten-Canvas abspielt (dieser Pfad steht in Wie HotPDF EMF- und WMF-Vektorgrafiken importiert):
// Zeicheneinheiten pro Punkt auf der aktuellen Seite. Spiegelt die Projektion,
// die HotPDF benutzt: 1 auf einer über Width/Height bemessenen Seite, sonst
// (document Resolution / 72) * (page Resolution / 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 ist in Punkt; SetFont erwartet Zeicheneinheiten
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
Die Bibliothek wendet dieselbe Regel auf ihre eigenen Punkt-Konstanten an. Der 12-Punkt-Font, mit dem jede neue Seite beginnt, wird jetzt mit dem internen Einheiten-pro-Punkt-Faktor multipliziert, er ist also bei jeder Resolution 12 Punkt. DrawChart, dessen Ränder, Beschriftungsgrößen und Strichstärken alle hart verdrahtete Punkte sind, läuft jetzt mit temporär auf 1 gesetzter Skala. Was absichtlich in Zeicheneinheiten bleibt, sind öffentliche Parameter-Defaults wie die DrawQRCode-Modulgröße und die Default-Tabellenschriftgröße: Sie sind Teil des API-Vertrags, bei Resolution 144 bedeuten sie also die Hälfte von dem, was sie bei 72 bedeuten. Wenn Sie Reports aus einer Vorlage bemessen, behandelt der Leitfaden zur Report-Ausgabe mit Fonts und Bildern in HotPDF, woher diese Werte üblicherweise kommen
Wie prüfen Sie, dass ein Layout von der Resolution unabhängig ist?
Der verlässlichste Check ist ein Byte-Vergleich: Rendern Sie dieselbe Seite einmal bei Resolution 72 und einmal bei 144 mit verdoppelten Koordinaten und Größen, die unkomprimierten Content-Streams müssen identisch sein. Beide Läufe landen nach der Projektion auf denselben Punkt-Werten, jede Differenz ist also ein Wert, der die Umrechnung ausgelassen hat. So prüft die HotPDF-Testsuite Absätze, Tabellen, HTML-Import, XFA-Flattening, Bögen, Metafiles und Bilder. Dieselbe Technik funktioniert für Ihren eigenen Report-Code mit fast keinem Aufwand:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // lesbare Content-Streams
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) und RenderPage('r144.pdf', 144, 2)
// müssen byte-identische Seiten-Content-Streams erzeugen
Prüfen Sie die Operatoren, die Zahlen tragen: Td, Tm, Tf, re, w und die TJ-Arrays. Bytes auf Dateiebene unterscheiden sich weiterhin im Erzeugungsdatum und in der /ID, vergleichen Sie also die Streams, nicht ganze Dateien. Eine Abweichung zeigt fast immer auf eine der zwei Fallen oben: eine Seite, die über Width umgestellt wurde, oder ein Punkt-Wert, der unverändert in SetFont gelangt. Wenn Sie neu bei den Zeichenaufrufen selbst sind, beginnen Sie mit dem HotPDF-TextOut-Walkthrough zu Größe, Stil und Rotation, und kommen Sie dann zurück und schalten die Resolution um, sobald Ihr Layout UserWidth liest. Vollständige API-Details und Trial-Downloads gibt es auf der HotPDF Delphi PDF component-Seite