Artikel Teknis

HotPDF Resolution di Delphi: Unit Gambar dan UserWidth

Di HotPDF Component, THotPDF.Resolution mendefinisikan unit gambar: setiap koordinat X dan Y, setiap margin, ukuran yang diberikan ke SetFont, dan hasil TextWidth serta GetWideTextWidth diukur dalam 1/Resolution inci. THPDFPage.Width dan Height tidak mengikutinya dan tetap dalam poin, jadi batas layout harus diambil dari UserWidth dan UserHeight yang read-only. Alasan lazim menyentuh Resolution adalah porting: report engine yang sudah berpikir dalam 1/96 atau 1/144 inci lebih mudah dipindahkan ketika sisi PDF berbicara dalam satuan yang sama daripada ketika setiap call site diberi faktor konversi. Itu bekerja dengan baik, selama Anda tahu angka mana yang pindah ke satuan baru dan mana yang tertinggal

Apa yang sebenarnya diubah THotPDF.Resolution?

THotPDF.Resolution hanya mengubah cara HotPDF membaca angka yang Anda berikan; PDF yang ditulisnya sama. Setter-nya dua baris: SetResolution menyimpan nilai dan menyetel DocScale := Value / 72. Sejak saat itu, XProjection dan YProjection membagi setiap koordinat dengan DocScale pada jalur masuk ke content stream, dan SetFont membagi ukuran dengan cara yang sama sebelum mencatatnya. User space PDF defaultnya 1/72 inci (ISO 32000-1 §8.3.2.3), jadi pada Resolution default 72 proyeksinya identitas dan pada 144 satu unit gambar setengah poin. Tak ada entri /UserUnit yang ditulis. Atribut halaman itu, ditambahkan di PDF 1.6, adalah hal terpisah yang HotPDF ekspos sebagai THPDFPage.SetUserUnit. Satu detail yang menjebak orang yang datang dari tutorial TextOut: koordinat halaman berjalan dari sudut kiri atas dengan Y bertambah ke bawah, karena YProjection menghitung atas MediaBox dikurangi Y yang diskalakan, dan itu tetap berlaku di setiap Resolution

Cara THotPDF.Resolution mendefinisikan unit gambar di Delphi: setter menyimpan DocScale sebagai Resolution dibagi 72, lalu XProjection, YProjection dan SetFont membagi setiap koordinat dan ukuran pada jalur masuk ke content stream, sehingga Resolution 72 adalah pemetaan identitas dan Resolution 144 membuat satu unit gambar setengah poin sementara halaman tetap berjalan kiri-atas dengan Y ke bawah
Tak ada yang bergerak di file output — hanya makna angka yang Anda berikan yang berubah, itulah kenapa content stream yang sama muncul di 72 dan 144
var
  Pdf: THotPDF;
  Page: THPDFPage;
  Margin: Single;
  Title: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Resolution := 144;               // 1 unit gambar = 1/144 inci
    Pdf.BeginDoc;
    Page := Pdf.CurrentPage;             // A4: Width = 595, UserWidth = 1190
    Margin := 144;                       // satu inci dalam unit gambar
    Page.SetFont('Arial', [fsBold], 28); // 28/144 inci, font 14 pt
    Title := 'INVOICE 2026-0417';
    // Rata kanan terhadap tepi halaman yang diukur dalam satuan yang sama
    Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
      Margin, 0, Title);
    Page.SetLineWidth(2);                // garis 1 pt
    Page.MoveTo(Margin, Margin + 48);
    Page.LineTo(Page.UserWidth - Margin, Margin + 48);
    Page.Stroke;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Kenapa Page.Width tak sepaham dengan koordinat saya di Resolution 144?

THPDFPage.Width dan Height melaporkan halaman dalam poin apa pun Resolution dokumennya, sementara koordinat Anda dalam 1/Resolution inci, jadi di 144 halaman tampak selebar setengah dari ukuran sebenarnya. Halaman A4 terbaca Width = 595 dan Height = 842 di Resolution 72 dan tetap terbaca 595 dan 842 di 144, padahal tepi kanannya sebenarnya di X = 1190. UserWidth dan UserHeight, ditambahkan di v2.766.0, mengembalikan Width * DocScale, yaitu ukuran halaman dalam satuan yang Anda pakai menggambar. Sebelum keduanya ada, library mencampur keduanya secara internal, dan gejalanya di Resolution 144 dramatis: paragraf wrap setelah setiap karakter, THPDFTable.Render mendorong setiap baris ke halaman baru, dan HTML importer maupun XFA flattener sama-sama menggambar kontennya setengah ukuran, form hasil flattening berdesakan di sudut kiri atas. Layout paragraf, rendering tabel, import HTML, pemusatan EMF, clip halaman WMF, dan diagnostik layout kini semuanya membaca ukuran user-unit. Kode layout Anda sendiri harus begitu juga: apa pun yang membandingkan dengan koordinat gambar (margin kanan, tes pindah halaman, perhitungan pemusatan) sebaiknya di UserWidth dan UserHeight, tidak pernah di Width dan Height

Jebakan satu: mengisi Width atau Height memindahkan halaman ke poin

Menyetel Page.Width atau Page.Height diam-diam mengubah halaman menjadi UserDefined, dan halaman UserDefined mengabaikan DocScale sepenuhnya, jadi semua yang Anda gambar di atasnya setelahnya dalam poin, bukan 1/Resolution inci. Setter-nya tua dan menerima poin memang by design, itulah kenapa maknanya dibiarkan. Proyeksi untuk halaman UserDefined polos saja, X + MinX, dan SetFont menyimpan ukuran tanpa diubah. Di Resolution 144 hasilnya halaman yang kontennya mendadak keluar dua kali lebih besar dari halaman sebelumnya. Library sendiri pernah membuat kesalahan persis ini: halaman lanjutan paragraf dulu menyalin ukuran halaman sebelumnya lewat Width, dan setiap halaman overflow pindah ke poin. Halaman-halaman itu kini menyalin Size, Orientation, dan Resolution halaman sebagai gantinya, dan hanya jatuh ke Width dan Height ketika halaman aslinya memang sudah UserDefined

Dua jalan keluar, tergantung kebutuhan Anda. Kalau lembar standar sudah cukup, setel Page.Size dan Page.Orientation dan terus menggambar dalam satuan Resolution Anda. Kalau Anda benar-benar butuh ukuran halaman kustom, terimalah bahwa ia halaman poin dan gambar dalam poin; UserWidth sama dengan Width di sana, jadi kode layout yang selalu membaca UserWidth tetap bekerja di kedua jenis halaman. Unit test menguncinya: di Resolution 144 halaman A4 melaporkan UserWidth 1190, tapi setelah Width := 500 dan Height := 400 ia melaporkan 500 dan 400. Halaman termuat berperilaku sama, karena halaman yang dibangun ulang dari PDF yang sudah ada hanya tahu MediaBox-nya dalam poin dan menggambar dalam poin. Halaman yang dibuat dokumen ini mempertahankan satuannya sendiri ketika Anda pindah lalu kembali lewat CurrentPageNumber, yang sudah berlaku sejak v2.766.26

Kenapa Page.Width tak sepaham dengan koordinat Anda di Resolution 144 di HotPDF: Width dan Height tetap dalam poin sementara menggambar memakai 1/144 inci, sehingga halaman A4 terbaca 595 tapi tepi kanannya terletak di UserWidth 1190, dan mengisi Width memindahkan halaman ke UserDefined yang mengabaikan DocScale, sehingga paragraf wrap per karakter, tabel pecah per baris dan ukuran SetFont terpangkas separuh
Apa pun yang membandingkan dengan koordinat gambar sebaiknya di UserWidth dan UserHeight — di halaman poin UserDefined keduanya berimpit, jadi kode layout yang sama selamat di keduanya

Jebakan dua: kenapa ukuran font keluar setengah ukuran?

Ukuran font yang awalnya poin keluar setengah ukuran di Resolution 144 karena SetFont memperlakukan argumen ukurannya sebagai unit gambar dan mengonversinya ke poin sebelum menyimpannya. Secara internal, SetFont menyimpan ASize / DocScale * DPI di objek font saat ini, jadi nilai tersimpannya selalu poin. Library tersandung ini dua kali: font fallback di WideTextOutBoxEx dan halaman lanjutan paragraf sama-sama menyerahkan nilai poin tersimpan itu kembali ke SetFont, yang menskalakannya untuk kedua kalinya dan memangkas teks jadi separuh. Kode Anda tak bisa membaca ukuran tersimpannya, tapi bug yang sama muncul setiap kali nilai poin dari tempat lain sampai ke SetFont: sebuah TFont.Size dari form VCL, ukuran di definisi report, panjang CSS pt. Konversikan lebih dulu, dan masukkan Resolution halaman sendiri beserta kasus UserDefined ke dalam faktornya, seperti yang dilakukan playback metafile saat memutar ulang Canvas halaman (lihat cara HotPDF mengimpor grafik vektor EMF dan WMF untuk jalur itu):

// Unit gambar per poin di halaman saat ini. Mencerminkan proyeksi
// yang dipakai HotPDF: 1 di halaman yang diukur lewat Width/Height, selain itu
// (Resolution dokumen / 72) * (Resolution halaman / 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 dalam poin; SetFont mengharapkan unit gambar
  Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
    Font.Size * UnitsPerPoint(Pdf));
end;

Library menerapkan aturan yang sama pada konstanta poin miliknya sendiri. Font 12 poin yang menjadi awal setiap halaman baru kini dikalikan faktor internal unit-per-poin, jadi ia 12 poin di Resolution mana pun. DrawChart, yang margin, ukuran label, dan lebar garisnya semuanya poin hard-coded, kini berjalan dengan skala yang sementara disetel ke 1. Yang tetap dalam unit gambar, memang disengaja, adalah default parameter publik seperti ukuran modul DrawQRCode dan ukuran font tabel default: mereka bagian dari kontrak API, jadi di Resolution 144 maknanya setengah dari makna di 72. Kalau Anda mengukur report dari template, panduan output report dengan font dan gambar di HotPDF membahas dari mana nilai-nilai itu biasanya berasal

Bagaimana memverifikasi bahwa sebuah layout bebas Resolution?

Pemeriksaan yang paling bisa diandalkan adalah perbandingan byte: render halaman yang sama di Resolution 72 lalu lagi di 144 dengan setiap koordinat dan ukuran digandakan, dan content stream tak terkompresinya harus identik. Kedua run mendarat pada nilai poin yang sama setelah proyeksi, jadi perbedaan apa pun adalah nilai yang melewatkan konversinya. Beginilah test suite HotPDF memeriksa paragraf, tabel, import HTML, XFA flattening, arc, metafile, dan gambar. Teknik yang sama bekerja untuk kode report Anda sendiri dengan harness nyaris nol:

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 yang terbaca
    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) dan RenderPage('r144.pdf', 144, 2)
// harus menghasilkan content stream halaman yang identik byte-nya
Cara memverifikasi ketiadaan ketergantungan Resolution di kode HotPDF Delphi: render layout yang identik dua kali, sekali di Resolution 72 dengan skala 1 dan sekali di 144 dengan setiap koordinat dan ukuran font digandakan, lalu mensyaratkan content stream tak terkompresi yang identik byte-nya — ketidakcocokan menunjuk halaman yang dipindah ke UserDefined lewat Width atau nilai poin tak terkonversi yang sampai ke SetFont
Kedua run mendarat pada nilai poin yang sama setelah proyeksi, jadi perbedaan apa pun adalah angka yang melewatkan konversinya — harness yang sama yang diandalkan test suite HotPDF

Periksa operator yang membawa angka: Td, Tm, Tf, re, w, dan array TJ. Byte level file tetap akan berbeda di tanggal pembuatan dan /ID, jadi bandingkan stream-nya, bukan seluruh file. Ketidakcocokan hampir selalu menunjuk salah satu dari dua jebakan di atas: halaman yang diubah ukurannya lewat Width, atau nilai poin yang diberikan langsung ke SetFont. Kalau Anda baru pada panggilan menggambarnya sendiri, mulai dari walkthrough TextOut HotPDF untuk ukuran, style, dan rotasi, lalu kembali dan ganti Resolution begitu layout Anda membaca UserWidth. Detail API lengkap dan unduhan trial ada di halaman HotPDF Delphi PDF component