W HotPDF Component THotPDF.Resolution definiuje jednostkę rysowania: każda współrzędna X i Y, każdy margines, rozmiar podawany do SetFont oraz wyniki TextWidth i GetWideTextWidth mierzone są w 1/Resolution cala. THPDFPage.Width i Height nie idą za tym i zostają w punktach, więc granice layoutu muszą pochodzić z tylko do odczytu UserWidth i UserHeight. Zwykły powód dotykania Resolution to port: silnik raportów, który już myśli w 1/96 albo 1/144 cala, łatwiej się przenosi, gdy strona PDF mówi tą samą jednostką, niż gdy każde miejsce wywołania dostaje współczynnik przeliczenia. To działa dobrze, dopóki wiesz, które liczby przeniosły się do nowej jednostki, a które zostały z tyłu
Co THotPDF.Resolution tak naprawdę zmienia?
THotPDF.Resolution zmienia tylko to, jak HotPDF czyta liczby, które mu podajesz; zapisywany PDF jest ten sam. Setter to dwie linie: SetResolution przechowuje wartość i ustawia DocScale := Value / 72. Od tego momentu XProjection i YProjection dzielą każdą współrzędną przez DocScale w drodze do strumienia treści, a SetFont dzieli rozmiar tak samo, zanim go zapamięta. Przestrzeń użytkownika PDF domyślnie to 1/72 cala (ISO 32000-1 §8.3.2.3), więc przy domyślnym Resolution 72 projekcja jest tożsamością, a przy 144 jedna jednostka rysowania to pół punktu. Żaden wpis /UserUnit nie jest pisany. Ten atrybut strony, dodany w PDF 1.6, to osobna rzecz, którą HotPDF wystawia jako THPDFPage.SetUserUnit. Szczegół, który łapie ludzi przychodzących z samouczków TextOut: współrzędne strony biegną od lewego górnego rogu, z Y rosnącym w dół, bo YProjection liczy górę MediaBoxa minus przeskalowane Y, i to zostaje prawdą przy każdym Resolution
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // 1 jednostka rysowania = 1/144 cala
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // jeden cal w jednostkach rysowania
Page.SetFont('Arial', [fsBold], 28); // 28/144 cala, font 14 pt
Title := 'INVOICE 2026-0417';
// Wyrównanie do prawej względem krawędzi strony mierzonej tą samą jednostką
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // linia 1 pt
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Dlaczego Page.Width nie zgadza się z moimi współrzędnymi przy Resolution 144?
THPDFPage.Width i Height raportują stronę w punktach, cokolwiek mówi Resolution dokumentu, podczas gdy twoje współrzędne są w 1/Resolution cala, więc przy 144 strona wygląda na dwukrotnie węższą, niż jest. Strona A4 czyta Width = 595 i Height = 842 przy Resolution 72 i nadal czyta 595 i 842 przy 144, gdzie prawa krawędź faktycznie leży przy X = 1190. UserWidth i UserHeight, dodane w v2.766.0, zwracają Width * DocScale, czyli rozmiar strony w jednostce, którą rysujesz. Zanim powstały, biblioteka mieszała te dwie rzeczy wewnętrznie, a objawy przy Resolution 144 były widowiskowe: akapity łamały się po każdym znaku, THPDFTable.Render wypychał każdy wiersz na nową stronę, a importer HTML i spłaszczacz XFA rysowały swoją treść w połowie rozmiaru, spłaszczony formularz stłoczony w lewym górnym rogu. Łamanie akapitów, renderowanie tabel, import HTML, centrowanie EMF, klip strony WMF i diagnostyka layoutu czytają teraz wszystkie rozmiar w jednostce użytkownika. Twój własny kod layoutu powinien też: cokolwiek porównuje ze współrzędną rysowania (prawy margines, test łamania strony, obliczenie centrowania) należy na UserWidth i UserHeight, nigdy na Width i Height
Pułapka pierwsza: przypisanie Width albo Height przełącza stronę na punkty
Ustawienie Page.Width albo Page.Height po cichu zmienia stronę na UserDefined, a strona UserDefined ignoruje DocScale w całości, więc wszystko, co na niej potem narysujesz, jest w punktach, nie w 1/Resolution cala. Setter jest stary i przyjmuje punkty z założenia, dlatego jego znaczenie zostawiono w spokoju. Projekcja dla strony UserDefined to gołe X + MinX, a SetFont przechowuje rozmiar bez zmian. Przy Resolution 144 skutkiem jest strona, której treść nagle wychodzi dwa razy większa niż na stronie poprzedniej. Biblioteka popełniła dokładnie ten błąd sama: strony kontynuacji akapitów kopiowały kiedyś rozmiar poprzedniej strony przez Width i każda strona przepełnienia przełączała się na punkty. Te strony kopiują teraz Size, Orientation i Resolution strony, a do Width i Height spadają dopiero, gdy oryginalna strona już była UserDefined
Dwa wyjścia, zależnie od potrzeb. Jeśli standardowy arkusz wystarczy, ustaw Page.Size i Page.Orientation i rysuj dalej w swojej jednostce Resolution. Jeśli naprawdę potrzebujesz niestandardowego rozmiaru strony, zaakceptuj, że to strona punktowa, i rysuj w punktach; UserWidth równa się tam Width, więc kod layoutu zawsze czytający UserWidth działa dalej na obu rodzajach stron. Test jednostkowy przypina to: przy Resolution 144 strona A4 raportuje UserWidth 1190, ale po Width := 500 i Height := 400 raportuje 500 i 400. Wczytane strony zachowują się tak samo, bo strona przebudowana z istniejącego PDF-a zna tylko swój MediaBox w punktach i rysuje w punktach. Strony stworzone przez ten dokument trzymają własne jednostki, gdy się przełączysz i wrócisz przez CurrentPageNumber, i tak jest od v2.766.26
Pułapka druga: dlaczego rozmiary fontów wychodzą o połowę?
Rozmiar fontu, który zaczął jako punkty, wychodzi o połowę przy Resolution 144, bo SetFont traktuje swój argument rozmiaru jako jednostki rysowania i przelicza go na punkty przed zapisaniem. Wewnętrznie SetFont przechowuje ASize / DocScale * DPI w bieżącym obiekcie fontu, więc przechowywana wartość to zawsze punkty. Biblioteka potknęła się o to dwa razy: fallback fontu w WideTextOutBoxEx i strona kontynuacji akapitu obie oddawały tę zapisaną wartość punktową z powrotem do SetFont, które skalowało ją drugi raz i poławiało tekst. Twój kod nie może przeczytać zapisanego rozmiaru, ale ten sam błąd wychodzi za każdym razem, gdy wartość punktową z cudzej strony dociera do SetFont: TFont.Size z formularza VCL, rozmiar w definicji raportu, długość pt z CSS. Przelicz ją najpierw i wlicz do współczynnika własne Resolution strony oraz przypadek UserDefined, tak robi odtwarzanie metafile przy powtórce Canvas strony (ta ścieżka jest opisana w artykule o tym, jak HotPDF importuje grafikę wektorową EMF i WMF):
// Jednostki rysowania na punkt na bieżącej stronie. Odbija projekcję
// HotPDF: 1 na stronie wymiarowanej przez Width/Height, inaczej
// (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 jest w punktach; SetFont oczekuje jednostek rysowania
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
Biblioteka stosuje tę samą regułę do własnych stałych punktowych. Font 12-punktowy, od którego zaczyna się każda nowa strona, jest teraz mnożony przez wewnętrzny współczynnik jednostek-na-punkt, więc ma 12 punktów przy każdym Resolution. DrawChart, którego marginesy, rozmiary etykiet i grubości linii to wszystko zaszyte punkty, biegnie teraz z tymczasowo ustawioną skalą 1. W jednostkach rysowania, z premedytacją, zostają publiczne domyślne wartości parametrów, jak rozmiar modułu DrawQRCode i domyślny rozmiar fontu tabeli: one są częścią kontraktu API, więc przy Resolution 144 znaczą połowę tego, co znaczą przy 72. Jeśli wymiarujesz raporty z szablonu, przewodnik o wyjściu raportów z fontami i obrazami w HotPDF omawia, skąd te wartości zwykle pochodzą
Jak zweryfikować, że layout jest niezależny od Resolution?
Najbardziej niezawodne sprawdzenie to porównanie bajtów: wyrenderuj tę samą stronę przy Resolution 72, a potem przy 144 z każdą współrzędną i rozmiarem podwojonym, i nieskompresowane strumienie treści muszą być identyczne. Oba biegi lądują na tych samych wartościach punktach po projekcji, więc każda różnica to wartość, która przeskoczyła przeliczenie. Tak zestaw testów HotPDF sprawdza akapity, tabele, import HTML, spłaszczanie XFA, łuki, metafile i obrazy. Ta sama technika działa dla twojego własnego kodu raportów przy niemal zerowym osprzęcie:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // czytelne strumienie treści
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)
// muszą produkować identyczne bajt w bajt strumienie treści stron
Sprawdź operatory niosące liczby: Td, Tm, Tf, re, w i tablice TJ. Bajty na poziomie pliku nadal będą się różnić datą utworzenia i /ID, więc porównuj strumienie, a nie całe pliki. Niezgodność niemal zawsze wskazuje jedną z dwóch pułapek z góry: stronę przeskalowaną przez Width albo wartość punktową podaną prosto do SetFont. Jeśli jesteś świeży w samych wywołaniach rysowania, zacznij od przewodnika HotPDF TextOut po rozmiarze, stylu i obrocie, a potem wróć i przełącz Resolution, gdy twój layout czyta UserWidth. Pełne szczegóły API i pobrania próbne są na stronie HotPDF Delphi PDF component