Artikel Teknis

Pratinjau Cetak PDF Library for Delphi dan Output Device-Context di Delphi

Merender halaman PDF ke device context Windows untuk pratinjau cetak menaruh tiga sistem koordinat dalam satu baris kode, dan mereka jarang sepakat. Halaman PDF diukur dalam poin dengan titik asal di kiri bawah. DC layar diukur dalam piksel dengan titik asal di kiri atas dan faktor zoom yang Anda pilih. DC printer, yang seharusnya diramalkan pratinjau itu, mengukur piksel pada resolusi perangkat tetapi menempatkan titik asalnya di sudut area tercetak, bukan di sudut lembarnya. Salah pada salah satu saja dan pratinjaunya tampak baik-baik saja sementara halaman tercetak keluar bergeser, terskala, atau terpotong di satu tepi. Gejala lazimnya adalah formulir bergaris tepi yang terpratinjau di tengah lalu tercetak dengan garis atas dan kirinya teriris, karena printer laser tidak bisa menaruh tinta di beberapa milimeter terluar dan tak ada yang memberi tahu pratinjaunya. losLab PDF Library (PDF Library for Delphi) menutupi seluruh jalur itu dengan pemanggilan rendering ke device context, sebuah lapisan konfigurasi printer virtual, dan bitmap pratinjau yang dihasilkan dari metrik printer itu sendiri, dan bagian terakhir inilah yang membuat pratinjaunya jujur soal margin tersebut

Geometri kertas bukan geometri area tercetak

Dua persegi panjang menggambarkan target cetak mana pun, dan selisih di antara keduanya adalah tempat sebagian besar bug pratinjau bersarang. Persegi panjang kertas adalah lembar fisiknya. Persegi panjang area tercetak adalah wilayah yang lebih kecil yang benar-benar dapat dijangkau mesin cetak, tersorok ke dalam oleh margin perangkat keras yang berbeda per model printer dan kadang per baki. Lapisan pencetakan pustaka ini mengukur keduanya. Kelas TPLPrinter di bawahnya memaparkan PageWidth dan PageHeight untuk area tercetak, FullPageWidth dan FullPageHeight untuk lembar penuh, serta PrintOffsetX bersama PrintOffsetY untuk jarak antara titik asal keduanya, semuanya dalam piksel perangkat pada resolusi yang dilaporkan GetDPI. Pratinjau yang jujur menyusutkan angka-angka yang sama itu ke resolusi layar alih-alih melukis halaman ke persegi panjang apa pun yang kebetulan dimiliki kontrolnya. Lewatkan langkah itu dan pratinjaunya diam-diam mengandaikan margin nol, satu-satunya nilai yang tidak dipakai printer sungguhan mana pun

Diagram PDF Library for Delphi tentang lembar kertas penuh versus persegi panjang yang dapat dicetak lebih kecil, dengan PrintOffsetX dan PrintOffsetY menandai margin perangkat keras di antara titik asal keduanya
Persegi paper adalah lembar fisik sementara persegi printable adalah yang sanggup dijangkau print engine, dan celah antara origin keduanya adalah tempat sebagian besar bug preview bersembunyi

Pratinjau layar lewat RenderPageToDC

Untuk kontrol pratinjau di layar, RenderPageToDC(DPI, Page, DC) menggambar halaman dari dokumen yang dimuat langsung ke device context GDI mana pun, entah itu kanvas TPaintBox, bitmap di luar layar, atau DC metafile. Argumen DPI menentukan zoom-nya. 96 mendekati tampilan 100% pada layar klasik, dan menggandakannya menggandakan ukuran hasil render

procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
  // ketiganya adalah keadaan pustaka yang lengket, bukan parameter per pemanggilan:
  FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
  FPdf.SetRenderDCErasePage(1);
  FPdf.SetRenderCropType(0);
  FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;

Jebakannya, jalur render DC dikemudikan oleh keadaan pustaka yang lengket, bukan oleh parameter per pemanggilan. SetRenderDCOffset, SetRenderDCErasePage, dan SetRenderCropType masing-masing bertahan sampai ada yang mengubahnya, sehingga loop thumbnail yang berjalan setelah pengguna menyetel tampilan terzoom akan mewarisi offset atau crop apa pun yang ditinggalkan jalur kode sebelumnya. Gejalanya adalah pratinjau yang melenceng hanya pada urutan navigasi tertentu, dan itu kira-kira jenis bug yang paling menyiksa untuk direproduksi. Menetapkan semua keadaan yang relevan di awal handler paint, seperti di atas, tidak berbiaya apa pun dan melenyapkan seluruh kelas masalah itu. Ada pengali kedua yang bersembunyi di dekatnya. Resolusi keluaran efektifnya adalah skala render dikali argumen DPI, dan meski SetRenderScale berdefault 1.0, ia juga bertahan begitu diubah, sehingga fitur ekspor yang sempat menaikkannya diam-diam menskalakan ulang setiap pratinjau berikutnya sampai ada yang mengembalikannya

Penampil yang bisa digulir dan pengecatan ulang sebagian punya varian khusus. RenderPageToDCClip menerima spesifikasi clip berikut device context-nya, sehingga membatalkan satu pita jendela hanya mengecat ulang pita itu alih-alih meraster ulang seluruh halaman. Pada zoom tinggi di halaman berformat besar, itulah bedanya antara penampil yang mengikuti scrollbar dan penampil yang mengekor berlepotan di belakangnya

Pekerjaan cetak yang cocok dengan pratinjaunya

Sisi pencetakan bekerja lewat printer virtual. NewCustomPrinter mengkloning printer sistem menjadi konfigurasi privat milik pustaka, dan SetupPrinter menyesuaikan klon itu tanpa menyentuh DevMode seluruh mesin: kertas masuk sebagai setting 1 (sebuah konstanta DMPAPER_*) dan orientasi sebagai setting 11. Imbalannya adalah isolasi. Sebuah layanan dapat mencetak label A4 sementara printer default host tetap di Letter, dan tidak ada yang perlu dipulihkan sesudahnya

PDF Library for Delphi: Alur dari printer bawaan sistem melalui NewCustomPrinter dan SetupPrinter ke pekerjaan cetak terisolasi yang tak pernah menyentuh DevMode seluruh mesin
SetupPrinter mengarahkan ulang klon privat milik library sehingga sebuah service dapat mencetak A4 sementara printer default host mempertahankan DevMode Letter-nya tanpa tersentuh
var
  Pdf: TPDFlib;
  Virt: WideString;
  Opt: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.LoadFromFile('report.pdf', '') <> 1 then
      raise Exception.Create('load failed');
    Virt := Pdf.NewCustomPrinter(Pdf.GetDefaultPrinterName);
    Pdf.SetupPrinter(Virt, 1, 9);        // setting 1 = kertas, DMPAPER_A4
    Pdf.SetupPrinter(Virt, 11, 1);       // setting 11 = orientasi, 1 = potret
    Opt := Pdf.PrintOptions(1, 1, 'Monthly Report');  // pas ke kertas, auto-rotate + tengah
    Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
  finally
    Pdf.Free;
  end;
end;

PrintOptions pantas dibaca dengan saksama. Ia mengembalikan handle opsi yang wajib Anda oper ke PrintDocument atau PrintPages; ia bukan keadaan ambien. Membangun opsinya lalu lupa mengoper handle-nya gagal secara senyap. Pekerjaannya tetap tercetak dengan nilai default, dan tak seorang pun menyadarinya sampai kebijakan pas-ke-kertas yang diharapkan ternyata menghasilkan halaman kebesaran yang terpotong. Argumen penskalaan halaman adalah tempat kebijakan itu tinggal. Tanpa penskalaan, akurasi dimensinya terjaga, dan itu penting untuk formulir yang diukur dengan penggaris. Pas-ke-kertas menskalakan ulang segalanya ke lembarnya. Perkecil-halaman-besar membiarkan halaman normal apa adanya dan hanya turun tangan ketika sebuah halaman melampaui area tercetak, dan itu biasanya default yang tepat untuk kumpulan dokumen campuran. Flag auto-rotate-and-center menangani halaman lanskap tanpa jalur kode kedua

Aplikasi yang sudah mengelola TPrinter lewat alur dialog VCL dapat menyerahkannya langsung. PrintDocumentToPrinterObject dan PrintPagesToPrinterObject menerima instance TPrinter yang sudah dikonfigurasi, sehingga dialog cetak standar tetap menjadi permukaan konfigurasi bagi pengguna sementara pustaka menangani rendering halamannya. Mencampur kedua pendekatan itu dalam satu jalur kode cenderung memunculkan kembali pergeseran geometri yang justru hendak dibunuh seluruh pekerjaan ini, jadi pilih satu. Rute printer virtual cocok untuk layanan tanpa penunggu; rute TPrinter cocok untuk aplikasi interaktif

Keluaran selektif bekerja dengan cara yang sama. PrintPages menerima string rentang, jadi mengoper nama printer virtual, '2-5,12', dan handle opsinya akan mencetak halaman 2 sampai 5 dan 12 dengan kontrak geometrinya utuh, dan sintaks yang sama menggerakkan varian cetak-ke-berkas. Varian berkas itulah jawaban praktis untuk lingkungan tanpa penunggu yang tidak punya perangkat fisik terpasang: menguji regresi geometri cetak di server build yang sama sekali tidak punya antrean driver. Render dokumen yang sama lewat opsi yang sama menjadi artefak berkas pada setiap build, dan regresi geometri berubah menjadi sebuah diff alih-alih laporan pelanggan tiga pekan kemudian

Bitmap pratinjau dengan metrik printer itu sendiri

Pratinjau yang dirender pada 96 DPI terhadap ukuran halaman yang diandaikan menjawab pertanyaan yang salah. Ia memperlihatkan seperti apa halamannya, bukan apa yang akan ditaruh printer ini di kertas ini. GetPrintPreviewBitmapToString menutup celah itu dengan membangun pratinjau dari printer kustom yang sama dan handle opsi yang sama dengan pekerjaan cetak nantinya, sehingga ukuran kertas, orientasi, kebijakan penskalaan, rotasi, dan offset perangkat keras semuanya ikut memasok bitmap-nya. Apa yang kembali adalah apa yang akan tampak di lembar itu

PDF Library for Delphi: Kontras antara pratinjau layar berukuran halaman asumsi yang salah menyatakan margin dengan bitmap setia-printer yang dibangun dari printer dan handle opsi milik pekerjaan itu sendiri
GetPrintPreviewBitmapToString menggambar preview dari handle printer kustom dan opsi yang sama seperti pekerjaan finalnya, sehingga bitmap menampilkan margin dan rotasi yang benar-benar akan diterima lembar
procedure ShowPrinterTruePreview(Pdf: TPDFlib; const Virt: WideString; Opt: Integer);
var
  Data: AnsiString;
  Strm: TMemoryStream;
  Bmp: TBitmap;
begin
  Data := Pdf.GetPrintPreviewBitmapToString(Virt, 1, Opt, 1200, 0);
  Strm := TMemoryStream.Create;
  try
    Strm.WriteBuffer(PAnsiChar(Data)^, Length(Data));
    Strm.Position := 0;
    Bmp := TBitmap.Create;
    try
      Bmp.LoadFromStream(Strm);
      PreviewImage.Picture.Assign(Bmp);
    finally
      Bmp.Free;
    end;
  finally
    Strm.Free;
  end;
end;

Argumen MaxDimension membatasi sisi panjang bitmap. 1200 piksel tetap tajam untuk dialog pratinjau dan menjaga pemakaian memori tetap sederhana bahkan untuk gambar teknik berukuran E, di mana render resolusi penuh pada 600 DPI milik printer akan membengkak sampai bergigabyte-gigabyte

Mengingat pilihan printer pengguna

Dialog cetak yang lupa pengaturannya antar sesi melahirkan tiket dukungannya sendiri. Pasangan DevMode, GetPrinterDevModeToString dan SetPrinterDevModeFromString, menyerialkan konfigurasi driver lengkap sebuah printer menjadi string legap yang bisa Anda simpan di preferensi pengguna dan pulihkan pada sesi berikutnya, termasuk opsi khusus driver yang tidak repot dimodelkan API generik mana pun. Simpan printer berdasarkan namanya dari GetPrinterNames, jangan pernah berdasarkan indeks daftar. Urutan indeks berubah setiap kali sebuah printer ditambahkan atau dihapus, sehingga indeks yang tersimpan diam-diam menunjuk perangkat yang salah ketika daftarnya bergeser. GetDefaultPrinterName menutupi kasus cadangan ketika perangkat yang diingat sudah lenyap sama sekali

Pemilihan baki melengkapi kisah persistensi ini. GetPrinterBins melaporkan sumber kertas yang dipaparkan sebuah driver, dan itu penting untuk alur kerja kop surat di mana halaman satu ditarik dari baki kop surat dan sisanya dari kertas polos. Itu kebijakan yang pengguna harapkan diingat aplikasi bersama segala hal lain, dan pekerjaan cetak yang mendarat di kertas yang salah terbaca sebagai bug bahkan ketika setiap byte PDF-nya sudah benar

Pertahankan satu mesin untuk pratinjau dan cetak

Satu keputusan terakhir diam-diam mengatur kesetiaan hasil. Pemilihan mesin rendering berlaku untuk tujuan layar maupun printer, sehingga godaannya adalah mempratinjau dengan mesin cepat dan mencetak dengan mesin presisi. Tahan godaan itu. Menggerakkan pratinjau dan pekerjaan cetak lewat mesin yang berbeda memunculkan kembali persis pergeseran kesetiaan yang hendak dihapus oleh pratinjau yang setia pada printer, dan ia melakukannya dengan cara yang baru tampak di atas kertas. Pertukaran antara mesin bawaan, Cairo, dan PDFium ditimbang di rendering PDF multi-mesin di Delphi; pilih satu dan pakai di kedua sisi

Dokumen yang terlalu besar untuk dimuat dengan nyaman sebelum dicetak dapat dibuka lewat jalur direct-access yang diuraikan di gabung, pecah, dan direct access untuk PDF besar, yang merender halaman ke device context dari sebuah file handle tanpa membangun pohon dokumen. Referensi API pencetakan selengkapnya ada di halaman produk losLab PDF Library for Delphi