Artikel Teknis

Mengimpor Vektor EMF dan WMF ke PDF Delphi dengan HotPDF

HotPDF, komponen PDF native untuk Delphi dan C++Builder, mengimpor metafile Windows EMF dan WMF dengan menerjemahkan langsung setiap record GDI menjadi operator PDF alih-alih meratakan file menjadi bitmap: gradient fill menjadi pattern axial shading PDF, hatch brush menjadi tiling pattern PDF, dan sebuah gate path-state terpusat mencegah record yang cacat merusak output. Setiap chart yang bisa diekspor sebagai enhanced metafile oleh TChart, permukaan GDI+, atau TCanvas biasa adalah kandidat untuk jalur ini, dan perbedaannya langsung terlihat begitu seseorang memperbesar halaman atau mengirimkannya ke printer beresolusi tinggi

Alternatif yang secara default banyak dipakai developer Delphi adalah me-rasterisasi metafile menjadi bitmap sebelum menempatkannya di halaman, dan biayanya baru terlihat belakangan: bar chart yang tajam di layar berubah menjadi terlihat pecah begitu PDF dicetak pada 600 DPI atau diproyeksikan ke layar ruang rapat, dan region CAD berisi hatch fill akan runtuh menjadi satu persegi panjang abu-abu polos jika gaya fill tidak diteruskan. Membaca metafile sebagai program alih-alih sebagai gambar itulah yang menghindari kedua masalah tersebut, dan itu adalah jalur yang lebih sulit untuk diimplementasikan dengan benar, sehingga jebakan-jebakan di bawah ini layak diketahui sebelum sebuah laporan dirilis

Mengapa menginterpretasikan metafile alih-alih meratakannya menjadi bitmap?

HotPDF mempertahankan impor EMF dan WMF pada jalur vektor karena metafile Windows adalah rekaman urutan pemanggilan gambar GDI, bukan sebuah gambar, dan memutar ulang pemanggilan-pemanggilan itu sebagai operator path, teks, dan shading PDF itulah yang membuat hasilnya bisa diskalakan seperti sisa halaman lainnya. THPDFPage.ShowMetafile beserta rekannya ShowMetafileEx adalah titik masuk yang dipanggil aplikasi, dan keduanya menyerahkan metafile ke THPDFWmf, kelas yang menelusuri setiap record GDI dan menerjemahkannya. Perbedaan ini tidak mutlak, dan HotPDF tidak berpura-pura sebaliknya: record metafile yang benar-benar berupa data raster, misalnya blit bitmap StretchDIBits, disematkan sebagai Image XObject PDF sesungguhnya lewat AddImage dan ShowImage, pasangan pemanggilan yang sama yang dilalui gambar lain mana pun di halaman, alih-alih dipaksakan menjadi operator path yang tidak bisa mengekspresikan sebuah foto. Garis, fill, dan teks tetap vektor; piksel yang memang sudah berupa piksel di sumber tetap menjadi piksel di output. Pemanggilan paling sederhana tidak membutuhkan apa pun selain metafile yang sudah dimuat:

var
  Pdf: THotPDF;
  Chart: TMetafile;
begin
  Pdf := THotPDF.Create(nil);
  Chart := TMetafile.Create;
  try
    Chart.LoadFromFile('quarterly-revenue.emf');  // exported from TChart or GDI+
    Pdf.FileName := 'quarterly-report.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafile(Chart);
    Pdf.EndDoc;
  finally
    Chart.Free;
    Pdf.Free;
  end;
end;

Bagaimana interpreter mengubah koordinat GDI menjadi ruang halaman PDF?

HotPDF menjawabnya dengan satu kali pemindaian atas stream record milik metafile itu sendiri, bukan dengan membuat implementasi kedua dari GDI. THPDFWmf.Analyse membaca header metafile lewat pemanggilan Win32 GetEnhMetaFileHeader, mereset state gambar internalnya, lalu memanggil EnumEnhMetafile, API enumerasi yang sama yang digunakan sebuah metafile viewer, sehingga setiap record EMR_* sampai ke THPDFWmf.ExecuteRecord dalam urutan aslinya saat direkam. GDI menyatakan koordinat dari atas ke bawah dalam unit device atau logical yang dipilih oleh mapping mode milik metafile itu sendiri; halaman PDF menggunakan orientasi bawah-ke-atas dalam poin user-space, sistem koordinat yang dibahas di model gambar canvas milik HotPDF untuk path dan fill. Setiap handler record menyelesaikan ketidakcocokan itu lewat ScaleX dan ScaleY, yang memanggil ProjectX dan ProjectY untuk memutar ulang rumus window-ke-viewport milik GDI sendiri untuk mapping mode anisotropic dan isotropic, sehingga sebuah bentuk yang direkam selebar lima unit logical akan mendarat pada lebar yang benar dalam poin PDF apa pun extent window dan viewport yang diatur aplikasi sumber

Bagaimana gradient fill GDI menjadi shading pattern PDF?

Record EMR_GRADIENTFILL menjadi pattern axial shading Type 2 PDF sesungguhnya (ISO 32000-1 §8.7.4.5) kapan pun GDI merekamnya dalam salah satu dari dua mode rectangle. THPDFWmf.VEMRGradientFill membaca layout record itu langsung dari raw byte buffer, mengikuti struktur MS-EMF §2.3.1.6: sebuah array vertex berisi sudut RGBA 16-bit, diikuti daftar rectangle yang masing-masing merujuk dua dari vertex tersebut. Untuk GRADIENT_FILL_RECT_H, warna menyapu dari kiri ke kanan sepanjang garis tengah horizontal rectangle; untuk GRADIENT_FILL_RECT_V, warna menyapu dari atas ke bawah sepanjang garis tengah vertikal. Bagaimanapun caranya, kedua warna sudut dan koordinat rectangle yang telah diproyeksikan langsung masuk ke THotPDF.RegisterAxialGradient, yang mengembalikan nama pattern, dan halaman menggambar rectangle serta mengisinya lewat pattern tersebut (SetFillPattern) alih-alih pemanggilan SetRGBFillColor polos, sehingga header bergaris ala spreadsheet atau area plot gradient sebuah chart tetap mempertahankan blend-nya alih-alih runtuh menjadi satu warna rata-rata

Mode triangle Gouraud adalah celah yang diakui secara terus terang. Ketika field ulMode pada record melaporkan GRADIENT_FILL_TRIANGLE, VEMRGradientFill mengenalinya, mencatat log bahwa mode triangle belum diimplementasikan, lalu melewati rectangle tersebut alih-alih menebak-nebak dengan aproksimasi dua warna. Interpolasi per-vertex, per-piksel di seluruh mesh triangle sembarang tidak bisa direduksi menjadi axial atau radial shading dua-stop, dan mengekspresikannya dengan benar berarti harus memancarkan mesh shading Type 4 atau Type 5 PDF, keluarga shading yang sama yang juga dibiarkan tidak digambar oleh page renderer milik HotPDF saat membaca kembali sebuah PDF. Dua jalur kode yang tidak berkaitan mendarat pada batas yang sama: mesh shading adalah celah baik di sisi penulisan maupun sisi pembacaan, dan diagram sumber yang menggunakan triangle Gouraud untuk efek radial glow yang halus akan jatuh kembali ke apa pun brush solid terakhir, bukan sebuah aproksimasi yang dirender

Hatch brush menjadi tiling pattern, bukan abu-abu yang diratakan

Hatch brush GDI mempertahankan teksturnya di PDF karena THPDFWmf.SetBrushColor memeriksa CurrentBrush.lbStyle untuk BS_HATCHED sebelum pernah jatuh kembali ke solid fill, mengarahkan kasus itu ke SetHatchBrushPattern. Metode itu menulis sebuah content stream PDF berukuran 8-kali-8 unit berisi operator garis stroke, m, l, dan S, dipilih berdasarkan gaya hatch GDI: satu stroke horizontal atau vertikal untuk HS_HORIZONTAL dan HS_VERTICAL, tiga garis diagonal paralel untuk HS_FDIAGONAL dan HS_BDIAGONAL, serta kombinasi horizontal-plus-vertikal atau kedua-diagonal untuk HS_CROSS dan HS_DIAGCROSS. THotPDF.RegisterTilingPattern mendaftarkan content stream itu sebagai colored tiling pattern (PaintType 1, ISO 32000-1 §8.7.3.1) dengan XStep dan YStep sebesar 8 unit, dan halaman mengisinya lewat SetFillPattern dengan cara yang sama seperti axial shading. Denah lantai CAD atau gambar teknik yang mengandalkan hatch fill untuk membedakan material tetap mempertahankan bahasa visual itu di PDF alih-alih kehilangan setiap region menjadi abu-abu yang identik

Tidak setiap brush mendapat perlakuan itu, dan celah ini layak diketahui sebelum sebuah impor CAD dirilis. EMR_CREATEDIBPATTERNBRUSHPT, record untuk pattern brush bitmap-image kustom alih-alih salah satu dari enam gaya hatch bawaan GDI, hanya mendaftarkan handle-nya agar record SELECTOBJECT dan DELETEOBJECT yang muncul belakangan tetap konsisten; HotPDF belum mengekspos pipeline resource Pattern PDF untuk gambar tile sembarang, sehingga memilih brush tersebut jatuh ke fallback warna solid alih-alih tekstur sumber. Jika sebuah fill dirender rata padahal aslinya jelas menggunakan tekstur gambar berulang, brush sumbernya hampir pasti adalah pattern DIB kustom alih-alih hatch standar, dan itulah satu-satunya kasus yang layak diperiksa secara manual lebih dulu. Mengonfigurasi impor untuk gambar semacam itu tetap melalui objek opsi yang sama:

var
  Pdf: THotPDF;
  Drawing: TMetafile;
  Options: THPDFEmfOptions;
begin
  Pdf := THotPDF.Create(nil);
  Drawing := TMetafile.Create;
  Options := THPDFEmfOptions.Create;
  try
    Drawing.LoadFromFile('floor-plan.emf');
    Options.Assign(Pdf.EmfOptions);   // start from the document-wide defaults
    Options.Redraw := False;          // interpret the original EMF bytes, no GDI re-record pass
    Options.ShowNullBrush := True;    // keep explicitly unfilled CAD regions visible
    Options.UseFrame := True;         // clip output to the frame the EMF header declares
    Pdf.FileName := 'floor-plan.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
    Pdf.EndDoc;
  finally
    Options.Free;
    Drawing.Free;
    Pdf.Free;
  end;
end;

Apa yang mencegah metafile yang cacat merusak halaman?

Jawaban HotPDF adalah satu gate tunggal di puncak ExecuteRecord, bukan pemeriksaan defensif yang diulang di masing-masing dari sekitar delapan puluh handler record miliknya. Sebuah bracket path GDI, dibuka oleh EMR_BEGINPATH dan ditutup oleh EMR_ENDPATH atau EMR_ABORTPATH, dilacak oleh sebuah properti privat PathContinue yang didukung field FPathContinue. Selama bracket itu terbuka, ExecuteRecord hanya meloloskan record konstruksi path, varian move, line, polyline, polygon, polybezier, dan polydraw, ditambah CLOSEFIGURE serta sekumpulan kecil record transform dan DC-state seperti SETWORLDTRANSFORM, SAVEDC, dan RESTOREDC. Setiap tipe record lain yang sampai ke ExecuteRecord selama bracket masih terbuka, misalnya EXTTEXTOUT yang nyasar atau sebuah bitmap blit, langsung dibuang secara terpusat lewat satu Exit pada saat itu juga

Gate itu ada karena bracket path pada metafile yang ditulis manual, dihasilkan tool, atau sekadar rusak tidak dijamin hanya berisi apa yang seharusnya ditempatkan sebuah file yang well-formed di antara record buka dan tutupnya. Sebuah record text-output yang mendarat di antara EMR_BEGINPATH dan EMR_ENDPATH, tanpa gate, akan mencemari geometri path yang sedang dibangun atau memancarkan operator penampil teks PDF di tengah urutan yang seharusnya merupakan konstruksi path murni, dan kedua mode kegagalan itu adalah jenis yang muncul dari satu input cacat dari tool pihak ketiga, bukan sesuatu yang biasanya tercakup test suite normal. Memusatkan pemeriksaan di ExecuteRecord berarti masing-masing handler VEMR* tidak perlu bertahan sendiri-sendiri terhadap kemungkinan dipanggil pada waktu yang salah; gate itu memutuskannya sekali, sebelum dispatch, alih-alih delapan puluh kali setelahnya

Menempatkan chart vektor bersama teks dan gambar di satu halaman

Sebuah halaman laporan jarang hanya berisi chart, dan ShowMetafile dapat dikombinasikan dengan operator halaman lain milik HotPDF persis seperti pemanggilan gambar lainnya. Sebuah heading yang digambar dengan TextOut, bar chart berisi hatch fill yang diimpor sebagai EMF, dan sebuah logo yang ditempatkan dengan ShowImage semuanya bisa mendarat di halaman yang sama dalam content stream yang sama, masing-masing mempertahankan fidelitas aslinya, pola komposisi yang dibahas di panduan HotPDF untuk menata teks, font, dan gambar dalam sebuah laporan:

Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart);   // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);

Interpreter EMF dan WMF, pattern axial shading yang didaftarkannya untuk gradient fill, dan pemetaan tiling-pattern untuk hatch brush yang dijelaskan di sini semuanya disertakan sebagai bagian dari HotPDF Component standar untuk Delphi dan C++Builder, sebuah library VCL native tanpa ketergantungan DLL eksternal untuk semuanya