Technisch artikel

HotPDF Resolution in Delphi: tekeneenheden en UserWidth

In HotPDF Component definieert THotPDF.Resolution de tekeneenheid: elke X- en Y-coördinaat, elke marge, de grootte die aan SetFont doorgegeven wordt, en de resultaten van TextWidth en GetWideTextWidth worden gemeten in 1/Resolution inch. THPDFPage.Width en Height volgen hem niet en blijven in punten, dus layoutgrenzen moeten komen van de read-only UserWidth en UserHeight. De gebruikelijke reden om Resolution aan te raken is een portering: een rapport-engine die al in 1/96 of 1/144 inch denkt verhuist makkelijker als de PDF-kant dezelfde eenheid spreekt dan wanneer elke call site een conversiefactor krijgt. Dat werkt goed, zolang u maar weet welke getallen naar de nieuwe eenheid zijn verhuisd en welke achterbleven

Wat verandert THotPDF.Resolution eigenlijk?

THotPDF.Resolution verandert alleen hoe HotPDF de getallen leest die u doorgeeft; de PDF die hij schrijft is dezelfde. De setter is twee regels: SetResolution bewaart de waarde en zet DocScale := Value / 72. Vanaf dat moment delen XProjection en YProjection elke coördinaat door DocScale op weg naar de content stream, en SetFont deelt de grootte op dezelfde manier voordat hij hem opslaat. PDF user space is standaard 1/72 inch (ISO 32000-1 §8.3.2.3), dus bij de default Resolution van 72 is de projectie de identiteit en bij 144 is één tekeneenheid een halve punt. Er wordt geen /UserUnit-entry geschreven. Dat pagina-attribuut, toegevoegd in PDF 1.6, is een apart iets dat HotPDF aanbiedt als THPDFPage.SetUserUnit. Eén detail dat mensen vangt die vanuit de TextOut-tutorials komen: pagina-coördinaten lopen vanaf de linkerbovenhoek met Y naar beneden groeiend, want YProjection berekent de MediaBox-top min de geschaalde Y, en dat blijft bij elke Resolution zo

Hoe THotPDF.Resolution de tekeneenheid in Delphi definieert: de setter bewaart DocScale als Resolution gedeeld door 72, daarna delen XProjection, YProjection en SetFont elke coördinaat en grootte op weg naar de content stream, zodat Resolution 72 een identiteitsmapping is en Resolution 144 één tekeneenheid een halve punt maakt terwijl de pagina nog steeds linksboven begint met Y naar beneden
Niets in het uitvoerbestand verplaatst zich — alleen de betekenis van de getallen die u doorgeeft verandert, wat is waarom dezelfde content stream op 72 en 144 verschijnt
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 tekeneenheid = 1/144 inch
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // één inch in tekeneenheden
    Page.SetFont('Arial', [fsBold], 28); // 28/144 inch, een font van 14 pt
    Title := 'INVOICE 2026-0417';
    // Rechts uitlijnen op de paginarand gemeten in dezelfde eenheid
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // lijn van 1 pt
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Waarom klopt Page.Width bij Resolution 144 niet met mijn coördinaten?

THPDFPage.Width en Height rapporteren de pagina in punten, wat de document-Resolution ook is, terwijl uw coördinaten in 1/Resolution inch zijn, dus bij 144 oogt de pagina half zo breed als hij werkelijk is. Een A4-pagina leest Width = 595 en Height = 842 bij Resolution 72 en leest bij 144 nog steeds 595 en 842, terwijl de rechterrand in werkelijkheid op X = 1190 ligt. UserWidth en UserHeight, toegevoegd in v2.766.0, geven Width * DocScale terug, de paginagrootte in de eenheid waarin u tekent. Vóór ze bestonden, mengde de library de twee intern door elkaar, en de symptomen bij Resolution 144 waren dramatisch: alinea's braken na elk teken af, THPDFTable.Render duwde elke rij naar een nieuwe pagina, en zowel de HTML-importeur als de XFA-flattener tekende zijn content op halve grootte, met het afgeplatte formulier in de linkerbovenhoek gedrongen. Alinealayout, tabelrendering, HTML-import, EMF-centering, de WMF-paginaclip en layoutdiagnostiek lezen nu allemaal de user-unit-grootte. Uw eigen layoutcode hoort dat ook te doen: alles wat tegen een tekencoördinaat vergelijkt (een rechtermarge, een paginabreak-test, een centeringberekening) hoort op UserWidth en UserHeight, nooit op Width en Height

Valkuil één: Width of Height toewijzen zet de pagina om naar punten

Page.Width of Page.Height zetten stilletjes de pagina om naar UserDefined, en een UserDefined-pagina negeert DocScale volledig, dus alles wat u er daarna op tekent is in punten, niet in 1/Resolution inch. De setter is oud en neemt punten bij ontwerp, daarom is zijn betekenis met rust gelaten. De projectie voor een UserDefined-pagina is kaal X + MinX, en SetFont slaat de grootte ongewijzigd op. Bij Resolution 144 is het resultaat een pagina waarvan de content plotseling twee keer zo groot uitkomt als de pagina ervoor. De library maakte precies deze fout zelf: vervolgpagina's van alinea's kopieerden vroeger de grootte van de vorige pagina via Width, en elke overflow-pagina schoof door naar punten. Die pagina's kopiëren nu in plaats daarvan Size, Orientation en de pagina-Resolution, en vallen alleen terug op Width en Height als de oorspronkelijke pagina al UserDefined was

Twee uitwegen, afhankelijk van wat u nodig heeft. Als een standaardvel volstaat, zet Page.Size en Page.Orientation en blijf tekenen in uw Resolution-eenheid. Als u echt een eigen paginaformaat nodig heeft, accepteer dan dat het een puntenpagina is en teken in punten; UserWidth is daar gelijk aan Width, dus layoutcode die altijd UserWidth leest blijft op beide soorten pagina's werken. De unittest zet dit vast: bij Resolution 144 rapporteert een A4-pagina een UserWidth van 1190, maar na Width := 500 en Height := 400 rapporteert hij 500 en 400. Geladen pagina's gedragen zich hetzelfde, want een pagina die uit een bestaande PDF is opgebouwd kent alleen zijn MediaBox in punten en tekent in punten. Pagina's die dit document zelf maakte houden hun eigen eenheden aan als u wegswitcht en via CurrentPageNumber terugkomt, en dat is zo sinds v2.766.26

Waarom Page.Width bij Resolution 144 in HotPDF niet klopt met uw coördinaten: Width en Height blijven in punten terwijl tekenen 1/144 inch gebruikt, dus een A4-pagina leest 595 maar zijn rechterrand ligt op UserWidth 1190, en Width toewijzen zet de pagina om naar UserDefined, dat DocScale negeert, zodat alinea's per teken afbreken, tabellen per regel breken en SetFont-groottes halveren
Alles wat tegen een tekencoördinaat vergelijkt hoort op UserWidth en UserHeight — op een UserDefined-puntenpagina vallen de twee samen, dus dezelfde layoutcode overleeft beide

Valkuil twee: waarom komen fontgroottes op de halve grootte uit?

Een fontgrootte die als punten begon, komt bij Resolution 144 op de halve grootte uit, omdat SetFont zijn grootte-argument als tekeneenheden behandelt en hem naar punten omzet voordat hij hem opslaat. Intern slaat SetFont ASize / DocScale * DPI op in het huidige fontobject, dus de opgeslagen waarde is altijd in punten. De library struikelde hier twee keer over: de fontfallback in WideTextOutBoxEx en de alinea-vervolgpagina gaven die opgeslagen puntwaarde allebei terug aan SetFont, die hem een tweede keer schaalde en de tekst halveerde. Uw code kan de opgeslagen grootte niet lezen, maar dezelfde bug verschijnt zodra een puntwaarde van ergens anders SetFont bereikt: een TFont.Size uit een VCL-formulier, een grootte in een rapportdefinitie, een CSS-pt-lengte. Zet hem eerst om, en neem de eigen Resolution van de pagina en het UserDefined-geval op in de factor, zoals metafile playback doet wanneer hij de paginanvas afspeelt (zie hoe HotPDF EMF- en WMF-vectorafbeeldingen importeert voor dat pad):

// Tekeneenheden per punt op de huidige pagina. Spiegelt de projectie
// die HotPDF gebruikt: 1 op een pagina van formaat via Width/Height, anders
// (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 is in punten; SetFont verwacht tekeneenheden
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

De library past dezelfde regel toe op zijn eigen puntconstanten. Het font van 12 punten waarmee elke nieuwe pagina begint, wordt nu vermenigvuldigd met de interne eenheden-per-punt-factor, dus het is 12 punten bij elke Resolution. DrawChart, waarvan marges, labelgroottes en lijnbreedtes allemaal hardgecodeerde punten zijn, draait nu met de schaal tijdelijk op 1. Wat bewust in tekeneenheden blijft, zijn publieke parameterdefaults zoals de DrawQRCode-modulegrootte en de standaardtabelfontgrootte: ze horen bij het API-contract, dus bij Resolution 144 betekenen ze de helft van wat ze bij 72 betekenen. Als u rapporten van een template voorziet van maten, behandelt de gids over rapportuitvoer met fonts en afbeeldingen in HotPDF waar die waarden meestal vandaan komen

Hoe controleert u dat een layout Resolution-onafhankelijk is?

De betrouwbaarste controle is een bytevergelijking: render dezelfde pagina bij Resolution 72 en opnieuw bij 144 met elke coördinaat en grootte verdubbeld, en de ongecomprimeerde content streams moeten identiek zijn. Beide runs komen na projectie op dezelfde puntwaarden uit, dus elk verschil is een waarde die de conversie overgeslagen heeft. Zo controleert de HotPDF-testsuite alinea's, tabellen, HTML-import, XFA-flattening, bogen, metafiles en afbeeldingen. Dezelfde techniek werkt voor uw eigen rapportcode met bijna geen testopstelling:

procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.Compression := cmNone;       // leesbare 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) en RenderPage('r144.pdf', 144, 2)
// moeten byte-identieke paginastreams opleveren
Hoe u Resolution-onafhankelijkheid in HotPDF Delphi-code verifieert: render dezelfde layout twee keer, een keer bij Resolution 72 met schaal 1 en een keer bij 144 met elke coördinaat en fontgrootte verdubbeld, en eis dan byte-identieke ongecomprimeerde content streams — een mismatch wijst naar een pagina die via Width naar UserDefined is overgezet of een niet-geconverteerde puntwaarde die SetFont bereikt
Beide runs komen na projectie op dezelfde puntwaarden uit, dus elk verschil is een getal dat zijn conversie oversloeg — dezelfde testopstelling waar de HotPDF-testsuite op vertrouwt

Controleer de operators die getallen meedragen: Td, Tm, Tf, re, w en de TJ-arrays. Bytes op bestandsniveau verschillen nog steeds in de aanmaakdatum en de /ID, dus vergelijk de streams, niet hele bestanden. Een mismatch wijst bijna altijd naar een van de twee valkuilen hierboven: een pagina die via Width van grootte is veranderd, of een puntwaarde die rechtstreeks in SetFont is gestopt. Als u nieuw bent in de tekenaanroepen zelf, begin dan met de HotPDF TextOut-walkthrough voor grootte, stijl en rotatie, en kom daarna terug om de Resolution om te zetten zodra uw layout UserWidth leest. De volledige API-details en trial-downloads staan op de HotPDF Delphi PDF component-pagina