În HotPDF Component, THotPDF.Resolution definește unitatea de desen: fiecare coordonată X și Y, fiecare margine, dimensiunea pasată lui SetFont și rezultatele lui TextWidth și GetWideTextWidth se măsoară în 1/Resolution inch. THPDFPage.Width și Height nu o urmează și rămân în puncte, deci limitele de machetă trebuie luate din UserWidth și UserHeight read-only. Motivul obișnuit de a atinge Resolution e un port: un motor de rapoarte care gândește deja în 1/96 sau 1/144 inch e mai ușor de mutat când partea de PDF vorbește aceeași unitate decât când fiecare loc de apel primește un factor de conversie. Merge bine, atâta timp cât știți care numere s-au mutat în unitatea nouă și care au rămas în urmă
Ce schimbă de fapt THotPDF.Resolution?
THotPDF.Resolution schimbă doar felul în care HotPDF citește numerele pe care le pasați; PDF-ul scris e același. Setter-ul are două linii: SetResolution stochează valoarea și setează DocScale := Value / 72. De atunci, XProjection și YProjection împart fiecare coordonată la DocScale pe drumul spre content stream, iar SetFont împarte dimensiunea la fel înainte s-o înregistreze. User space-ul PDF are implicit 1/72 inch (ISO 32000-1 §8.3.2.3), deci la Resolution-ul implicit de 72 proiecția e identitatea, iar la 144 o unitate de desen e jumătate de punct. Nicio intrare /UserUnit nu e scrisă. Atributul acela de pagină, adăugat în PDF 1.6, e un lucru separat, pe care HotPDF îl expune ca THPDFPage.SetUserUnit. Un detaliu care prinde oamenii veniți din tutorialele TextOut: coordonatele paginii pornesc din colțul din stânga-sus cu Y crescând în jos, pentru că YProjection calculează top-ul MediaBox-ului minus Y-ul scalat, și rămâne așa la orice Resolution
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // 1 unitate de desen = 1/144 inch
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // un inch în unități de desen
Page.SetFont('Arial', [fsBold], 28); // 28/144 inch, un font de 14 pt
Title := 'INVOICE 2026-0417';
// Aliniere la dreapta față de muchia paginii măsurată în aceeași unitate
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // linie de 1 pt
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
De ce Page.Width nu se pune de acord cu coordinatele mele la Resolution 144?
THPDFPage.Width și Height raportează pagina în puncte indiferent de Resolution-ul documentului, în timp ce coordinatele voastre sunt în 1/Resolution inch, deci la 144 pagina pare de două ori mai îngustă decât e. O pagină A4 citește Width = 595 și Height = 842 la Resolution 72 și tot citește 595 și 842 la 144, unde muchia din dreapta e de fapt la X = 1190. UserWidth și UserHeight, adăugate în v2.766.0, întorc Width * DocScale, adică dimensiunea paginii în unitatea în care desenați. Înainte să existe, biblioteca amesteca cele două intern, iar simptomele la Resolution 144 erau spectaculoase: paragrafele se rupeau după fiecare caracter, THPDFTable.Render împingea fiecare rând pe o pagină nouă, iar importerul HTML și aplatizatorul XFA desenau conținutul lor la jumătate de mărime, forma aplatizată înghesuită în colțul din stânga-sus. Machetarea paragrafelor, randarea tabelelor, importul HTML, centrarea EMF, clip-ul de pagină WMF și diagnosticele de machetă citesc acum cu toții dimensiunea în unități de utilizator. Codul vostru de machetă ar trebui la fel: orice se compară cu o coordonată de desen (o margine dreaptă, un test de rupere de pagină, un calcul de centrare) ține de UserWidth și UserHeight, niciodată de Width și Height
Capcana întâi: asignarea lui Width sau Height trece pagina pe puncte
Setatul Page.Width sau Page.Height schimbă în tăcere pagina pe UserDefined, iar o pagină UserDefined ignoră cu desăvârșire DocScale-ul, deci tot ce desenați pe ea după aceea e în puncte, nu în 1/Resolution inch. Setter-ul e vechi și primește puncte din construcție, de-asta sensul i-a fost lăsat în pace. Proiecția pentru o pagină UserDefined e simplu X + MinX, iar SetFont stochează dimensiunea neschimbată. La Resolution 144 rezultatul e o pagină al cărei conținut iese deodată de două ori mai mare decât al paginii dinainte. Biblioteca a făcut exact greșeala asta pe cont propriu: paginile de continuare ale paragrafelor copiau pe vremuri dimensiunea paginii precedente prin Width, iar fiecare pagină de depășire trecea pe puncte. Paginile acelea copiază acum Size, Orientation și Resolution-ul paginii, și cad pe Width și Height doar când pagina originală era deja UserDefined
Două ieșiri, în funcție de ce aveți nevoie. Dacă o coala standard e de ajuns, setați Page.Size și Page.Orientation și continuați să desenați în unitatea Resolution-ului vostru. Dacă chiar vă trebuie o dimensiune de pagină proprie, acceptați că e o pagină în puncte și desenați în puncte; acolo UserWidth egalează Width-ul, deci codul de machetă care citește întotdeauna UserWidth merge în continuare pe ambele feluri de pagină. Testul unitar fixează asta: la Resolution 144, o pagină A4 raportează un UserWidth de 1190, dar după Width := 500 și Height := 400 raportează 500 și 400. Paginile încărcate se poartă la fel, pentru că o pagină reconstruită dintr-un PDF existent cunoaște doar MediaBox-ul ei în puncte și desenează în puncte. Paginile pe care acest document le-a creat își păstrează unitățile proprii când comutați departe și vă întoarceți prin CurrentPageNumber, cum e de la v2.766.26
Capcana a doua: de ce dimensiunile fonturilor ies la jumătate?
O dimensiune de font care a pornit ca puncte iese la jumătate la Resolution 144 pentru că SetFont tratează argumentul de dimensiune ca unități de desen și îl convertește în puncte înainte să-l stocheze. Intern, SetFont stochează ASize / DocScale * DPI în obiectul de font curent, deci valoarea stocată e întotdeauna în puncte. Biblioteca a alunecat de două ori pe aici: fallback-ul de font din WideTextOutBoxEx și pagina de continuare a paragrafelor au pasat amândouă acea valoare în puncte înapoi lui SetFont, care a scalat-o a doua oară și a înjumătățit textul. Codul vostru nu poate citi dimensiunea stocată, dar același bug apare ori de câte ori o valoare în puncte de proveniență străină ajunge la SetFont: un TFont.Size dintr-un formular VCL, o dimensiune dintr-o definiție de raport, o lungime CSS în pt. Convertiți-o întâi și includeți în factor Resolution-ul propriu al paginii și cazul UserDefined, cum face redarea metafile-urilor când re-redă pagina Canvas (vedeți cum importă HotPDF grafică vectorială EMF și WMF pentru calea aceea):
// Unități de desen per punct pe pagina curentă. Oglindește proiecția
// pe care o folosește HotPDF: 1 pe o pagină dimensionată prin Width/Height,
// altfel (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 e în puncte; SetFont așteaptă unități de desen
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
Biblioteca aplică aceeași regulă constantelor proprii în puncte. Fontul de 12 puncte cu care pornește fiecare pagină nouă e înmulțit acum cu factorul intern de unități-per-punct, deci e de 12 puncte la orice Resolution. DrawChart, ale cărui margini, dimensiuni de etichete și grosimi de linii sunt toate puncte bătute în cod, rulează acum cu scala setată temporar pe 1. Ce rămâne în unități de desen, din intenție, sunt implicitele de parametri publici precum dimensiunea de modul a lui DrawQRCode și dimensiunea implicită de font a tabelelor: fac parte din contractul API, deci la Resolution 144 înseamnă jumătate din ce înseamnă la 72. Dacă dimensionați rapoarte dintr-un șablon, ghidul despre output de rapoarte cu fonturi și imagini în HotPDF acoperă de unde vin de obicei valorile acelea
Cum verificați că o machetă e independentă de Resolution?
Verificarea cea mai de încredere e o comparație de octeți: randăți aceeași pagină la Resolution 72 și din nou la 144 cu fiecare coordonată și dimensiune dublate, iar content stream-urile necompresate trebuie să fie identice. Ambele rulări aterizează pe aceleași valori în puncte după proiecție, deci orice diferență e o valoare care a sărit peste conversie. Așa verifică suita de test HotPDF paragrafele, tabelele, importul HTML, aplatizarea XFA, arcele, metafile-urile și imaginile. Aceeași tehnică merge pentru propriul cod de rapoarte cu un harness aproape inexistent:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // content stream-uri lizibile
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) and RenderPage('r144.pdf', 144, 2)
// trebuie să producă content stream-uri de pagină identice octet cu octet
Verificați operatorii care cară numere: Td, Tm, Tf, re, w și tablourile TJ. Octeții la nivel de fișier vor diferi în continuare la data de creare și la /ID, deci comparați stream-urile, nu fișiere întregi. O nepotrivire arată aproape întotdeauna spre una dintre cele două capcane de mai sus: o pagină redimensionată prin Width sau o valoare în puncte pasată direct lui SetFont. Dacă sunteți nou în apelurile de desen în sine, porniți cu turul TextOut din HotPDF pentru dimensiune, stil și rotație, apoi întoarceți-vă și comutați Resolution-ul odată ce macheta voastră citește UserWidth. Detaliile complete API și descărcările de încercare sunt pe pagina componentei HotPDF Delphi PDF