Artikel Teknis

Load PDF yang Gagal Senyap di Delphi: Load Report PDFium

Di PDFium Component for Delphi and Lazarus, meng-assign TPdf.Active := True tidak pernah melempar exception saat PDF gagal dimuat: TPdf.SetActive menangkap setiap exception dan membiarkan komponen tidak aktif. Untuk melihat error yang sebenarnya, panggil TPdf.LoadDocument(Options, Report). Overload itu melempar ulang exception aslinya dan mengisi TPdfLoadReport dengan status load, kode error native PDFium, dan apakah tabel cross-reference harus dibangun ulang

Masalahnya biasanya muncul di kode batch. Pekerjaan ekstraksi tabel menyusuri folder berisi 13 PDF dunia nyata dengan satu TPdf bersama, dan 7 di antaranya kembali sebagai kegagalan. Tak satu pun dari 7 berkas itu yang benar-benar rusak. Blok except di sekitar load tak pernah menyala, log menyalahkan nama berkas yang salah, dan error pertama yang terlihat adalah EPdfError polos soal komponen tak aktif, dilempar dari pembacaan properti beberapa baris setelah load yang sebenarnya gagal. Dua perilaku yang terpisah menumpuk menghasilkan gambaran itu, dan keduanya bekerja sesuai desain

Mengapa TPdf.Active := True tidak melempar exception saat PDF gagal dimuat?

TPdf.SetActive membungkus LoadDocument dalam try..except yang menelan semua kelas exception dan sekadar membiarkan komponen tidak aktif. Penelanan itu disengaja: setter yang sama berjalan saat designer form men-toggle Active di IDE, dan path yang salah tak boleh meremukkan IDE. Saat runtime TPdf.Active hanya melaporkan ada-tidaknya handle dokumen native, jadi setelah load yang gagal ia terbaca False dan tak terjadi apa pun lagi. Apa pun yang sempat dilempar lenyap, entah EPdfError dari parser, error stream, atau EAccessViolation dari pdfium.dll yang ter-bind setengah jadi. Pesan DLL terperinci yang dijelaskan di mendiagnosis kegagalan load pdfium.dll di Delphi hanya sampai ke handler Anda lewat panggilan yang tidak menelannya

Dua jalur load di PDFium Component: meng-assign Active true menelan semua exception di setter dan menunda kegagalannya ke panggilan terjaga pertama, tempat CheckActive melempar EPdfError soal komponen tak aktif, sementara LoadDocument dengan TPdfLoadOptions dan TPdfLoadReport meng-audit header, startxref, xref, dan penanda akhir berkas, lalu melempar ulang exception asli dengan penyebab sebenarnya menempel
Penelanannya disengaja karena designer IDE berbagi setter yang sama; kode batch butuh overload yang melempar exception, melaporkan, dan menceritakan kisah asli berkasnya
Pdf.FileName := FileName;
try
  Pdf.Active := True;       // SetActive menelan exception load apa pun
except
  on E: Exception do
    Log.Add(FileName + ': ' + E.Message);   // tak pernah dieksekusi
end;
// Kegagalannya justru muncul di sini, sebagai EPdfError generik:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));

// Fix minimal untuk kode yang sudah ada: uji Active persis setelah assignment;
// sejak v3.122.1 LastLoadReport menyimpan teks error yang tertelan
Pdf.Active := True;
if not Pdf.Active then
  Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);

Kegagalannya akhirnya tampak di panggilan terjaga pertama. TPdf.PageCount, seperti kebanyakan properti dokumen, diawali CheckActive, yang melempar EPdfError yang menyebut komponen tapi bukan berkasnya dan bukan penyebabnya. Menguji Pdf.Active persis setelah assignment mengubah crash yang salah alamat jadi entri "gagal" yang jujur. Sebelum PDFiumPas v3.122.1 alasannya lenyap di titik itu; sejak v3.122.1 assignment yang gagal mengganti LastLoadReport dengan report plsFailed yang membawa teks errornya, jadi penyebabnya selamat. Objek exception-nya sendiri dan audit level byte tetap butuh entry point yang berbeda

Mengapa memakai ulang satu TPdf gagal mulai berkas kedua?

TPdf.FileName hanya bisa di-assign saat komponen tidak aktif, jadi instance bersama menolak berkas kedua bahkan sebelum mencoba memuatnya. TPdf.SetFileName diawali CheckInactive, dan guard yang sama melindungi Password dan FormFill. Setelah load pertama yang sukses, instance tetap aktif, assignment berikutnya melempar exception, dan kalau loop batch menangkap exception itu lalu melanjutkan, errornya mendarat di bawah nama berkas baru sementara dokumen lama masih terbuka. Tercampur dengan kegagalan load yang tertelan, log berhenti cocok dengan kenyataan. Di reproduksi 13 berkas itu, satu instance bersama melaporkan 7 kegagalan, sementara TPdf.Create(nil) segar per dokumen membuka ketiga belasnya. Menyetel Active := False di antara berkas juga berhasil, tapi satu instance per dokumen menjaga setiap berkas terisolasi secara konstruksi

Garis waktu satu TPdf PDFium bersama yang gagal mulai berkas kedua: setelah load pertama instance tetap aktif, assignment FileName berikutnya melempar exception di CheckInactive sebelum upaya load mana pun, dan loop batch mencatat errornya di bawah nama berkas baru sementara dokumen lama masih terbuka — jebakan di balik 7 kegagalan palsu dalam batch 13 berkas
SetFileName menjaga dengan CheckInactive, jadi instance bersama menolak berkas kedua sebelum mencobanya; isolasi setiap dokumen dengan TPdf miliknya sendiri dan log cocok dengan kenyataan lagi

Apa yang diberikan TPdf.LoadDocument dengan TPdfLoadReport?

TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) melempar exception yang sebenarnya dan juga memberi tahu apa yang terjadi dalam bentuk terstruktur. Overload berkas memuat FileName; overload saudaranya menerima TBytes atau pointer plus ukuran, dan LoadCustomDocument(AStream, AOwnsStream, Options, Report) menangani stream. Masing-masing memvalidasi opsinya, memeriksa bahwa instance tidak aktif, menjalankan audit level byte atas header, startxref, seksi-seksi xref, dan penanda %%EOF, lalu melakukan load native. Auditnya dibatasi oleh jenis batas yang sama yang dibahas di anggaran resource parser untuk PDF tak terpercaya: TPdfLoadOptions.Default menyetel AuditByteLimit ke 256 MiB, MaxIssues ke 256, MaxXrefSections ke 1024, dan MaxXrefEntries ke 4,000,000. Saat gagal, metode menyetel Report.Status := plsFailed dan melempar ulang; karena Report ditulis di tempat, isinya selamat dari exception, dan salinannya tersimpan di TPdf.LastLoadReport

Field-field report menjawab pertanyaan yang benar-benar dibutuhkan log batch. Status bernilai salah satu dari plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected, atau plsFailed. NativeErrorCode menampung FPDF_GetLastError, jadi FPDF_ERR_PASSWORD (4) memisahkan password yang hilang atau salah dari berkas rusak yang dilaporkan sebagai FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid, dan RecoveryRoute menyatakan apakah PDFium harus membangun ulang tabel xref, dan Issues mencantumkan tiap temuan audit dengan Code, Severity, Offset, ObjectNumber, dan MessageText, dengan IssuesTruncated terpasang saat MaxIssues memotong daftarnya

Pipeline LoadDocument milik PDFium Component dan TPdfLoadReport-nya: validasi opsi dan pemeriksaan tak aktif melempar exception sebelum report mana pun ada, audit byte menyusuri header, startxref, seksi xref, dan penanda akhir berkas, load native mencatat FPDF_GetLastError, dan hasilnya bercabang jadi loaded, loaded with recovery setelah rebuild xref, penolakan strict, atau failure
Status, NativeErrorCode, dan daftar issue menjawab yang dibutuhkan log batch; hanya overload beropsi yang menambah audit byte, sementara sejak v3.122.1 Active := True yang gagal tetap mencatat plsFailed di LastLoadReport
uses
  SysUtils, Classes, TypInfo, FPdfView, PDFium;

procedure ProcessBatch(Files, Log: TStrings);
var
  I: Integer;
  Pdf: TPdf;
  Options: TPdfLoadOptions;
  Report: TPdfLoadReport;
begin
  Options := TPdfLoadOptions.Default(plmCompatible);
  for I := 0 to Files.Count - 1 do
  begin
    Pdf := TPdf.Create(nil);          // satu instance per dokumen
    try
      Pdf.FileName := Files[I];
      try
        Pdf.LoadDocument(Options, Report);
      except
        on E: Exception do
        begin
          // Report terisi meski LoadDocument melempar exception
          if Report.NativeErrorCode = FPDF_ERR_PASSWORD then
            Log.Add(Files[I] + ': password required')
          else
            Log.Add(Format('%s: %s (%s)', [Files[I],
              GetEnumName(TypeInfo(TPdfLoadStatus), Ord(Report.Status)),
              E.Message]));
          Continue;
        end;
      end;
      if Report.UsedRecovery then
        Log.Add(Files[I] + ': opened after PDFium rebuilt the xref table');
      ExtractTables(Pdf, Log);
    finally
      Pdf.Free;
    end;
  end;
end;

Kapan sebaiknya Anda memuat dengan plmStrict?

Pakai plmStrict kapan pun berkas yang diperbaiki diam-diam lebih buruk daripada yang ditolak, seperti intake arsip, penanganan bukti, atau pipeline signing. PDFium diam-diam merekonstruksi tabel cross-reference yang rusak (ISO 32000-1 §7.5.4) dengan memindai berkas untuk objek-objeknya, yang bagus untuk viewer dan jadi masalah bagi apa pun yang harus memproses persis byte yang diberikan kepadanya. Setelah load native, komponen bertanya ke FPDF_DocumentHasValidCrossReferenceTable. Di mode plmCompatible sebuah rebuild menghasilkan plsLoadedWithRecovery plus peringatan plicNativeCrossReferenceRebuild. Di mode plmStrict komponen membongkar muatan dokumen, menyetel plsRejected, menambah plicStrictModeRejected, dan melempar EPdfError dengan "Strict PDF load rejected the document". Mode strict juga menolak error audit apa pun, dan TPdfLoadOptions.Default(plmStrict) menyalakan RequireFinalEndOfFileMarker, yang menaikkan %%EOF yang hilang atau data setelah yang terakhir (§7.5.5) dari peringatan jadi error. Audit xref melengkapi pemeriksaan level objek di memvalidasi object dan xref stream dengan PDFium VCL

function AcceptForArchive(const FileName: string; out Reason: string): Boolean;
var
  Pdf: TPdf;
  Report: TPdfLoadReport;
  I: Integer;
begin
  Result := False;
  Reason := '';
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    try
      Pdf.LoadDocument(TPdfLoadOptions.Default(plmStrict), Report);
      Result := True;               // xref valid, tanpa error audit
    except
      on E: EPdfError do
      begin
        Reason := E.Message;
        for I := 0 to High(Report.Issues) do
          if Report.Issues[I].Severity = plisError then
            Reason := Reason + sLineBreak + Format('  at offset %d: %s',
              [Report.Issues[I].Offset, Report.Issues[I].MessageText]);
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Di mana TPdf.LastLoadReport berhenti berkata jujur?

TPdf.LastLoadReport hanya lengkap setelah overload LoadDocument yang menerima opsi, karena hanya overload-overload itu yang menjalankan audit byte. Active := True yang sukses menulis report mode kompatibel tanpa audit byte, jadi AuditAttempted tetap False. Sebelum PDFiumPas v3.122.1, yang gagal tidak menulis apa pun, yang berarti di instance bersama LastLoadReport masih mendeskripsikan berkas sebelumnya, sering kali dengan plsLoaded yang menenangkan. Sejak v3.122.1 setiap load yang gagal mengganti reportnya: Active := True yang gagal, yang tetap membiarkan komponen tidak aktif tanpa melempar exception, dan panggilan LoadDocument polos atau LoadCustomDocument yang gagal mencatat plsFailed dengan teks errornya, lagi-lagi tanpa audit. Dua celah lagi berarti di praktik. Validasi opsi dan CheckInactive berjalan sebelum report diinisialisasi, jadi AuditByteLimit negatif atau instance yang sudah aktif melempar exception tanpa menghasilkan report. Dan NativeErrorCode bermakna hanya saat PDFium benar-benar mencoba parse-nya; untuk berkas yang hilang, wrapper melempar exception sebelum PDFium berjalan, jadi catat ErrorMessage dan teks exception-nya sebagai gantinya

Aturan praktisnya pendek. Pertahankan Active := True untuk viewer yang terikat designer, tempat komponen tak aktif adalah akhir yang bisa diterima. Di tempat lain, dan terutama di kode batch dan server, buat satu TPdf per dokumen, panggil LoadDocument(Options, Report), tangkap exception yang ia lempar, dan catat Report.Status, NativeErrorCode, dan Issues level error bersama nama berkasnya. Biayanya beberapa baris per call site, dan setiap kegagalan teratribusikan ke berkas yang benar dengan penyebab aslinya

API load report, mode strict, dan audit level byte dikirim bersama PDFium Component for Delphi, C++Builder and Lazarus, berdampingan dengan rendering, ekstraksi teks, pengisian form, dan validasi PDF/A