Artikel Teknis

Konfigurasi Library PDFium: Brotli Diam-diam Menukar Skia

Di PDFium Component for Delphi, menyalakan BrotliEnabled atau IsolatePerDocument di TPdfLibraryConfiguration dulu menukar build Skia bawaan ke renderer AGG tanpa error apa pun, karena kedua opsi itu menaikkan FPDF_LIBRARY_CONFIG ke versi tempat PDFium membaca m_RendererType secara harfiah. Sejak v3.123.0, renderer default tetap default milik DLL sendiri, dan sejak v3.125.0, permintaan Skia atau Fontations yang tak bisa dipenuhi DLL memicu EPdfError yang bisa ditangkap alih-alih mematikan proses

Tak satu pun dari kedua bug itu mengumumkan dirinya. Yang pertama menghasilkan halaman yang tampak baik, sekadar dirender rasterizer berbeda, dengan anti-aliasing dan tepi teks sedikit berbeda dari build yang Anda kirim dan tes. Yang kedua memang mengumumkan dirinya, dengan keras, dengan merobohkan proses host dari dalam inisialisasi native. Keduanya berasal dari tempat yang sama: struktur C berversi yang field-nya baru dihitung begitu nomor versinya bilang begitu, dan yang nilai nolnya bukan "tak diset" melainkan pilihan sungguhan

Bagaimana FPDF_LIBRARY_CONFIG memutuskan renderer mana yang dipakai PDFium?

FPDF_InitLibraryWithConfig berkonsultasi ke m_RendererType hanya ketika field Version struktur bernilai 4 atau lebih, dan dari versi itu ia memakai nilai apa adanya. Di bawah versi 4, PDFium mengabaikan field itu dan memilih default build, yaitu Skia pada build yang dikompilasi dengan PDF_USE_SKIA dan AGG di tempat lain

Setiap field berikutnya mengikuti pola yang sama. Struktur itu tumbuh satu kapabilitas demi satu kapabilitas, dan tiap kapabilitas datang bersama nomor versi baru. PDFium Component membangun struktur native di LoadLibrary dari TPdfLibraryConfiguration Anda dan menaikkan versi hanya sejauh yang dituntut opsi yang Anda set

Versi strukturField yang ditambahkanDisetel oleh
2m_pIsolate, m_v8EmbedderSlotSelalu ditulis; V8Isolate, V8EmbedderSlot
3m_pPlatformV8Platform bukan nil
4m_RendererTypeRenderer selain prpDefault
5m_FontLibraryTypeFontBackend selain pfbpDefault
6m_BrotliEnabledBrotliEnabled = True
7m_IsolatePerDocumentIsolatePerDocument = True

Jebakannya ada di dua baris terakhir. Versi itu kumulatif: struktur versi 6 juga struktur versi 4 dan versi 5, sehingga PDFium membaca m_RendererType dan m_FontLibraryType meski Anda hanya meminta Brotli. Apa pun yang berada di dua field itu pada saat itu menjadi renderer dan font backend, entah Anda bermaksud memilihnya atau tidak

Tangga versi FPDF_LIBRARY_CONFIG PDFium Component dari versi 2 sampai versi 7 yang memperlihatkan opsi TPdfLibraryConfiguration mana yang menambah m_RendererType, m_FontLibraryType, m_BrotliEnabled, dan m_IsolatePerDocument, serta kenapa versi kumulatif menjadikan field renderer bernilai nol pilihan AGG yang disengaja alih-alih nilai tak diset pada build mana pun
Tiap opsi menaikkan versi struktur dan setiap field sebelumnya tetap hidup, sehingga nol di m_RendererType tiba di PDFium sebagai permintaan AGG eksplisit

Mengapa menyalakan Brotli menukar renderer ke AGG?

Sebelum v3.123.0, PDFium Component menulis FPDF_RENDERERTYPE_AGG ke m_RendererType untuk prpDefault, sehingga konfigurasi apa pun yang mendorong struktur ke versi 6 atau 7 memaksakan AGG pada build Skia. Runtime pdfium.dll dan pdfium.v8.dll yang dikirim bersama komponen adalah build Skia, jadi ini mengenai deployment default, bukan yang eksotis

Pemetaan itu tampak tak berbahaya saat ditulis. Pada versi 2 atau 3, field itu tak pernah dibaca, jadi prpDefault memang berarti "apa pun yang dilakukan DLL". Begitu BrotliEnabled (versi 6) atau IsolatePerDocument (versi 7) masuk ke dalam persamaan, kode yang sama mengubah "tanpa preferensi" menjadi permintaan AGG eksplisit. Tak ada yang gagal. PDFium terinisialisasi normal, merender setiap halaman, dan tak mengembalikan kode error, karena dari sudut pandangnya caller meminta AGG dan menerima AGG

Pixel hash membuat penukaran itu terlihat di tempat screenshot gagal. Merender halaman pertama dokumen sampel yang sama di bawah tiga konfigurasi menghasilkan:

  • Konfigurasi default: hash 502D77C3711B4ACF
  • BrotliEnabled = True dengan Renderer dibiarkan di prpDefault: hash F75B5EB4728ADE87
  • prpAgg eksplisit: hash F75B5EB4728ADE87, identik dengan run Brotli

Perbaikan di v3.123.0 adalah fungsi publik PdfNativeRendererType, yang me-resolve TPdfRendererPreference menjadi nilai yang ditulis ke m_RendererType. prpAgg dan prpSkia terpetakan satu-satu. prpDefault kini dipetakan ke Skia ketika DLL yang termuat mengekspor FPDF_RenderPageSkia dan ke AGG jika tidak. Export itu dikompilasi di bawah kondisi PDF_USE_SKIA yang sama dengan default Skia itu sendiri, yang menjadikannya satu-satunya properti build yang bisa Anda amati dari luar DLL. Setelah perbaikan, konfigurasi Brotli menghasilkan hash yang sama dengan konfigurasi default

Perbandingan pixel hash PDFium Component yang memperlihatkan hash render Skia default 502D77C3711B4ACF, konfigurasi BrotliEnabled pra-v3.123.0 yang cocok dengan run prpAgg eksplisit ber-hash F75B5EB4728ADE87, dan wrapper yang sudah diperbaiki me-resolve prpDefault lewat export FPDF_RenderPageSkia kembali ke hash Skia asli
Pixel hash menangkap yang disembunyikan screenshot: menyalakan Brotli dulu merender setiap halaman dengan AGG, dan default yang sudah diperbaiki kini cocok dengan konfigurasi yang tak tersentuh

Font backend tak pernah mengalami masalah yang sama. m_FontLibraryType dibaca mulai versi 5, dan nilai nolnya, FPDF_FONTBACKENDTYPE_FREETYPE, juga default PDFium ketika field itu sama sekali tak dibaca. Menulis FreeType untuk pfbpDefault karena itu mereproduksi default native secara persis. Nilai nol tak selalu salah, ia sekadar tak pernah otomatis benar

Dengan v3.123.0 atau lebih baru, kode startup yang secara alami akan Anda tulis kini berbuat sesuai ucapannya:

uses
  PDFium;

procedure ConfigurePdfiumAtStartup;
var
  Config: TPdfLibraryConfiguration;
begin
  // Harus berjalan sebelum apa pun memuat library native
  Config := TPdfLibraryConfiguration.Default;
  Config.BrotliEnabled := True;   // menaikkan FPDF_LIBRARY_CONFIG ke versi 6
  // Renderer tetap prpDefault: ter-resolve ke Skia pada build yang mengekspor
  // FPDF_RenderPageSkia dan ke AGG pada build AGG-saja
  SetLength(Config.UserFontPaths, 1);
  Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
  ConfigurePdfLibrary(Config);
end;

Ingat bahwa BrotliEnabled hanya membuat stream /BrotliDecode PDF 2.0 bisa didekode ketika DLL itu sendiri dibangun dengan PDF_ENABLE_BROTLI. Flag itu adalah permintaan, dan pada build tanpa dukungan Brotli ia tak berefek apa pun. TPdfLibraryConfiguration.Hardened sama dengan Default kecuali AllowMachineTime False, yang memblokir JavaScript dokumen dari membaca jam sesungguhnya; itu titik awal yang masuk akal untuk pemrosesan sisi server atas file yang tak terpercaya

Apa yang terjadi ketika Anda meminta backend yang tak dimuat DLL?

PDFium tak mengembalikan error untuk renderer atau font backend yang tak ada di build: FPDF_InitLibraryWithConfig menggagalkan CHECK native, yang di Windows muncul sebagai breakpoint exception dan, tanpa structured exception handler mengelilingi panggilan itu, mengakhiri proses. Header-nya mengatakan persis begitu, memperingatkan bahwa nilai yang tak didukung "akan gagal serupa dengan crash seketika"

Dua kasus konkretnya adalah build AGG-saja yang menerima FPDF_RENDERERTYPE_SKIA, dan build tanpa Fontations yang menerima FPDF_FONTBACKENDTYPE_FONTATIONS. Runtime Skia bawaan berada di kelompok kedua: ia merender dengan Skia tapi memakai FreeType untuk font. Meminta prpSkia bersama pfbpFontations kepadanya menghasilkan External exception 80000003 di sisi Delphi. Ketika debugger atau exception handler kebetulan menangkapnya, situasinya tetap tak bisa dipulihkan:

  • PDFium tertinggal setengah terinisialisasi
  • Konfigurasi seluruh proses sudah tersegel, sehingga ConfigurePdfLibrary menolak konfigurasi yang sudah diperbaiki
  • Mencoba ulang dengan konfigurasi berbeda di proses yang sama tak lagi mungkin

Ini kegagalan yang berlawanan dengan bug Brotli. Di sana field memegang nilai yang tak dipilih siapa pun dan PDFium diam-diam menerimanya. Di sini field memegang nilai yang dipilih caller dengan sengaja dan PDFium sama sekali tak menerima diskusi soal itu. Keduanya adalah masalah yang harus diselesaikan wrapper sebelum panggilan native, karena setelahnya tak ada lagi yang bisa ditangkap

Cara PDFium Component melakukan precheck Skia dan Fontations

Sejak v3.125.0, LoadLibrary memvalidasi konfigurasi setelah mengikat export DLL dan sebelum memanggil FPDF_InitLibraryWithConfig, serta mengubah renderer atau font backend yang tak didukung menjadi EPdfError dengan pesan yang menyebut setting bermasalah dan alternatifnya. DLL di-unload dan konfigurasi dibuka segelnya, sehingga caller bisa memilih setting lain dan memuat lagi

Keputusannya sendiri berada di fungsi murni PdfLibraryConfigurationSupportError, yang menerima konfigurasi plus dua Boolean yang mendeskripsikan build dan mengembalikan string kosong ketika kombinasinya aman. Karena tak menyentuh state native sama sekali, Anda bisa memanggilnya dari test Anda sendiri dengan kombinasi kapabilitas apa pun. Di dalam LoadLibrary, kedua Boolean itu datang dari jenis bukti yang berbeda, dan keduanya layak mendapat tingkat kepercayaan berbeda:

  • Skia terdeteksi dari hadirnya export FPDF_RenderPageSkia, sinyal yang sama dengan yang dipakai PdfNativeRendererType. Export dan renderer Skia dikompilasi di bawah satu kondisi, sehingga pemeriksaannya presisi
  • Fontations tak punya export sendiri. Jejak satu-satunya adalah Rust font crate yang ia tarik ke dalam binary, sehingga PDFium Component memindai file library termuat mencari nama crate skrifa dan read-fonts (juga read_fonts). Pemindaian hanya berjalan ketika pfbpFontations diminta, dan file yang tak bisa dibaca dihitung "tanpa Fontations"

Pemeriksaan Fontations adalah heuristik, dan ia bisa keliru ke satu arah: build Fontations yang seluruh string itu dihapus darinya akan ditolak meski sebenarnya bisa bekerja. Trade itu diambil dengan sengaja. Penolakan palsu merampas satu exception yang bisa Anda tangkap dan satu fallback ke FreeType. Penerimaan palsu merampas proses Anda

Membuka segel sama pentingnya dengan pemeriksaannya. LoadLibrary menyegel konfigurasi di awal sekali proses pemuatan, sehingga tanpa reset itu, penolakan kapabilitas akan membiarkan ConfigurePdfLibrary menjawab setiap retry dengan EPdfError "konfigurasi library PDFium sudah tersegel". Jalur penolakan memanggil UnloadLibrary lebih dulu; panggilan FPDF_DestroyLibrary-nya aman pada titik itu karena PDFium belum terinisialisasi dan langsung kembali. Kegagalan muat lain, seperti DLL hilang atau ketidakcocokan arsitektur, mempertahankan segelnya, sehingga loop retry harus bisa membedakan keduanya:

uses
  SysUtils, PDFium;

function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
  Config: TPdfLibraryConfiguration;
begin
  Config := TPdfLibraryConfiguration.Default;
  Config.Renderer := prpSkia;
  ConfigurePdfLibrary(Config);
  try
    PDFium.LoadLibrary;   // berkualifikasi unit: Windows.LoadLibrary bernama sama
    Result := prpSkia;
  except
    on E: EPdfError do
    begin
      // Penolakan kapabilitas meng-unload DLL dan membuka segel konfigurasi.
      // DLL yang sama sekali gagal dimuat tetap tersegel: mencoba ulang tak menolong
      if PdfLibraryConfigurationSealed then
        raise;
      Config.Renderer := prpAgg;
      ConfigurePdfLibrary(Config);
      PDFium.LoadLibrary;
      Result := prpAgg;
    end;
  end;
end;

Perhatikan PDFium.LoadLibrary yang eksplisit. Di unit yang juga memakai Windows atau Winapi.Windows, LoadLibrary tanpa kualifikasi ter-resolve ke unit mana pun yang muncul terakhir di uses clause; ketika itu fungsi Win32, panggilan tanpa parameter gagal dikompilasi dengan error jumlah argumen yang tak mengatakan apa-apa soal PDFium

Alur precheck LoadLibrary PDFium Component ketika ConfigurePdfLibrary menyegel konfigurasi, pemeriksaan kapabilitas mengetes export FPDF_RenderPageSkia dan bukti string skrifa, permintaan tak didukung memicu EPdfError yang bisa ditangkap dan membuka segel untuk retry, sementara DLL yang tak pernah termuat membiarkan PdfLibraryConfigurationSealed true
Validasi berjalan setelah export terikat dan sebelum inisialisasi, sehingga backend yang absen gagal sebagai EPdfError yang bisa Anda tangkap alih-alih CHECK native yang mematikan proses

Validasi yang terjadi bahkan lebih awal

ConfigurePdfLibrary menolak beberapa kombinasi sebelum DLL mana pun terlibat, semuanya dengan EPdfError. FontBackend eksplisit, termasuk pfbpFreeType, mensyaratkan Renderer = prpSkia, karena PDFium hanya berkonsultasi ke font backend untuk renderer Skia. IsolatePerDocument mensyaratkan V8Isolate nil, karena PDFium menciptakan isolate-nya sendiri per dokumen dan menggagalkan CHECK native kalau Anda juga menyerahkannya satu. String kosong di UserFontPaths ditolak. Dan panggilan apa pun setelah percobaan muat pertama gagal dengan "konfigurasi library PDFium sudah tersegel"

Aturan terakhir itu punya konsekuensi praktis: Anda tak bisa mengintip DLL lebih dulu lalu mengonfigurasinya sesudahnya. GetSkiaRenderCapabilities, V8FeaturesAvailable, membuka dokumen, dan kebanyakan entry point lain memanggil LoadLibrary secara internal, yang menyegel konfigurasi di tempat. Memanggil UnloadLibrary belakangan juga tak membukanya kembali. Konfigurasi dulu, lalu muat, lalu bertanya, persis urutan yang seharusnya diikuti routine diagnostik:

uses
  SysUtils, PDFium, FPdfView;

function DescribePdfiumState: string;
var
  Config: TPdfLibraryConfiguration;
  Renderer: string;
begin
  Config := GetPdfLibraryConfiguration;   // salinan, aman untuk diperiksa
  if not PDFium.Loaded then
  begin
    if PdfLibraryConfigurationSealed then
      Exit('PDFium failed to load; configuration is sealed');
    Exit('PDFium not loaded; configuration can still change');
  end;
  // Resolusi yang sama dengan yang diterapkan LoadLibrary saat membangun FPDF_LIBRARY_CONFIG
  if PdfNativeRendererType(Config.Renderer,
    GetSkiaRenderCapabilities.PageRender) = FPDF_RENDERERTYPE_SKIA then
    Renderer := 'Skia'
  else
    Renderer := 'AGG';
  Result := Format('Renderer=%s Brotli=%s IsolatePerDocument=%s',
    [Renderer, BoolToStr(Config.BrotliEnabled, True),
     BoolToStr(Config.IsolatePerDocument, True)]);
end;

Mencatat baris itu sekali saat startup murah, dan itu hal pertama yang Anda inginkan di tiket dukungan yang berbunyi "teksnya tampak berbeda di server". PDFium.Loaded berkualifikasi unit karena alasan yang sama dengan LoadLibrary: di dalam method form atau komponen, Loaded polos terikat ke TComponent.Loaded

Dua cara struct konfigurasi C berversi keliru

Setiap struktur konfigurasi berversi, entah FPDF_LIBRARY_CONFIG, record cbSize Win32, atau ABI plugin, gagal dengan dua cara simetris, dan wrapper harus berjaga terhadap keduanya. Yang pertama mengisi field sambil membiarkan versi terlalu rendah; yang kedua menaikkan versi sambil membiarkan field pada nilai nol yang oleh library dibaca sebagai pilihan disengaja

  1. Field diset, versi terlalu rendah. Tulis m_BrotliEnabled = 1 ke struktur versi 2 dan PDFium tak pernah melihatnya. Panggilan sukses dan stream Brotli tetap tak terdekode. Pertahanannya adalah menurunkan versi dari field yang benar-benar dipakai, itulah yang dilakukan LoadLibrary, alih-alih meng-hard-code satu angka
  2. Versi cukup tinggi, field nol bermakna sesuatu. Naikkan versi ke 6 dan setiap field sampai versi 6 kini hidup. FillChar men-nol-kan m_RendererType menjadi FPDF_RENDERERTYPE_AGG, renderer sungguhan, bukan "tak diset". Pertahanannya adalah menulis setiap field yang dicakup versi terpilih dengan nilai yang disengaja, dan me-resolve "default" terhadap build aktual alih-alih mengasumsikannya

Aturan ketiga menyusul untuk nilai yang bisa membuat callee crash: validasilah terhadap apa yang sanggup dilakukan binary sebelum panggilan, memakai bukti terkuat yang tersedia, dan jujurlah di kode maupun dokumentasi ketika bukti itu heuristik. Simbol yang diekspor adalah bukti. Nama crate di string table adalah tebakan yang bagus

Referensi cepat: konfigurasi library PDFium Component

  • Panggil ConfigurePdfLibrary sekali, sebelum apa pun memuat DLL; kueri kapabilitas atau muat dokumen apa pun menyegelnya
  • Upgrade ke v3.123.0 atau lebih baru jika Anda menyetel BrotliEnabled atau IsolatePerDocument dan mengharapkan keluaran Skia dari runtime bawaan
  • Biarkan Renderer di prpDefault kecuali Anda butuh rasterizer tertentu; ia kini ter-resolve ke default build pada setiap versi struktur
  • Pakai PdfNativeRendererType dengan GetSkiaRenderCapabilities.PageRender untuk mencatat renderer mana yang benar-benar aktif
  • Harapkan EPdfError, bukan crash, untuk prpSkia pada DLL AGG-saja atau pfbpFontations pada DLL tanpa Fontations di v3.125.0 atau lebih baru
  • Setelah penolakan kapabilitas, PdfLibraryConfigurationSealed False dan Anda boleh mengonfigurasi ulang; setelah kegagalan muat DLL ia tetap True
  • Perlakukan deteksi Fontations sebagai heuristik dan simpan fallback FreeType
  • Tulis PDFium.LoadLibrary dan PDFium.Loaded dengan nama unit untuk menghindari benturan nama Win32 dan TComponent

Kalau DLL gagal sebelum konfigurasi sempat berperan, mulailah dari diagnosis kegagalan muat DLL PDFium di Delphi, dan untuk cara komponen menemukan binary yang tepat di tiap platform lihat memuat library native PDFium di target mana pun. Begitu renderer beres, taktik render cache dan zoom mulus membahas cara menjaga rendering halaman tetap cepat di viewer

PDFium Component membungkus engine PDFium untuk Delphi dan C++Builder dengan pemeriksaan konfigurasi semacam ini, sehingga inisialisasi native gagal sebagai exception Pascal yang bisa Anda tangani alih-alih eksekusi proses yang berakhir. Detail produk dan unduhan ada di halaman produk PDFium Component for Delphi