PDFium Component Docs

Contoh Pemrograman PDFium VCL

Operasi Dokumen Dasar

Memuat dan Menampilkan Dokumen PDF

Pdf1.FileName := 'C:\MyDocument.pdf';
Pdf1.Active := True;
ShowMessage('Halaman: ' + IntToStr(Pdf1.PageCount));
ShowMessage('Judul: ' + Pdf1.Title);
ShowMessage('Penulis: ' + Pdf1.Author);

var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile('C:\MyDocument.pdf');
    Pdf1.LoadDocument(Stream, True);
    Pdf1.Active := True;
    ShowMessage('PDF dimuat dari stream');
  finally
    Stream.Free;
  end;
end;

Membuat Dokumen PDF Baru

Pdf1.CreateDocument;
Pdf1.Active := True;
Pdf1.AddPage(0, 612, 792);
Pdf1.PageNumber := 1;
Pdf1.AddText('Hello World!', 'Arial', 12, 100, 700, clBlack, $FF, 0.0);
Pdf1.UpdatePage;
Pdf1.SaveAs('C:\NewDocument.pdf');
Pdf1.Active := False;

Operasi Teks

Mengekstrak Teks dari PDF

var
  PageText: WString;
  RectText: WString;
begin
  Pdf1.PageNumber := 1;
  PageText := Pdf1.Text;
  Memo1.Text := PageText;
  RectText := Pdf1.TextInRectangle(100, 100, 500, 200);
  ShowMessage('Teks dalam persegi panjang: ' + RectText);
end;

Mencari Teks

var
  Position: Integer;
begin
  Position := Pdf1.FindFirst('Hello', [seCaseSensitive]);
  while Position >= 0 do
  begin
    ShowMessage('Ditemukan pada karakter: ' + IntToStr(Position));
    Position := Pdf1.FindNext;
  end;
end;

Menambahkan Teks dengan Pemformatan

Pdf1.PageNumber := 1;
	Pdf1.AddText('Judul Dokumen', 'Times New Roman', 16, 100, 750, clBlue);
	Pdf1.AddText('Ini adalah konten dokumen.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;

Memeriksa Metadata Karakter dan Font

var
  I: Integer;
  MetricSize: Single;
  FontBytes: TBytes;
begin
  Pdf1.PageNumber := 1;
  for I := 0 to Pdf1.ObjectCount - 1 do
    if Pdf1.ObjectType[I] = otText then
    begin
      MetricSize := 12.0;
      FontBytes := Pdf1.FontData[I];
      Memo1.Lines.Add('Objek ' + IntToStr(I));
      Memo1.Lines.Add('Nama dasar: ' + Pdf1.FontBaseName[I]);
      Memo1.Lines.Add('Keluarga: ' + Pdf1.FontFamilyName[I]);
      Memo1.Lines.Add('Tertanam: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
      Memo1.Lines.Add('Bobot: ' + IntToStr(Pdf1.FontWeight[I]));
      Memo1.Lines.Add('Sudut miring: ' + IntToStr(Pdf1.FontItalicAngle[I]));
      Memo1.Lines.Add('Ascent: ' + FloatToStr(Pdf1.FontAscent[I, MetricSize]));
      Memo1.Lines.Add('Descent: ' + FloatToStr(Pdf1.FontDescent[I, MetricSize]));
      Memo1.Lines.Add('Byte data font: ' + IntToStr(Length(FontBytes)));
    end;

  if Pdf1.CharacterCount > 0 then
  begin
    Memo1.Lines.Add('Ukuran karakter pertama: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
    Memo1.Lines.Add('Bobot karakter pertama: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
    Memo1.Lines.Add('Sudut karakter pertama: ' + FloatToStr(Pdf1.CharacterAngle[0]));
  end;
end;

Proyek Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties, dan Demo\Lazarus\FontProperties menampilkan metadata yang sama melalui TPdf dan TPdfView

Menyusun Laporan Ekstraksi Konten Multikategori

Proyek Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab, dan Demo\Lazarus\ContentExtractionLab mengubah ekstraksi konten menjadi satu alur kerja. Proyek ini membuka atau membuat PDF, membiarkan pengguna memilih metadata, teks halaman, objek halaman, gambar, lampiran, tautan, anotasi, bookmark, dan metrik font / karakter, lalu menghasilkan ringkasan serta laporan TXT atau JSON yang rinci. Lab ini juga menyertakan seleksi kategori semua / tidak ada, salin laporan ke clipboard, dan tindakan simpan PDF sumber agar pengguna dapat menyimpan sample PDF yang dihasilkan di samping laporan ekstraksinya. Demo lama ExtractText, ExtractImages, Attachment, dan FontProperties tetap menjadi contoh yang terfokus; ExtractText mendukung opsi rentang halaman dan pemisah halaman untuk menghasilkan output teks per halaman di Delphi, C++Builder, dan Lazarus, sedangkan ContentExtractionLab menjadi sampel inventaris yang mencakup seluruh area

Operasi Formulir

Bekerja dengan Field Formulir

var
  I: Integer;
  FieldInfo: TPdfFormFieldInfo;
begin
  Pdf1.FormField[0] := 'John';
  for I := 0 to Pdf1.FormFieldCount - 1 do
  begin
    FieldInfo := Pdf1.FormFieldInfo[I];
    ShowMessage(FieldInfo.Name + ': ' + Pdf1.FormField[I]);
  end;
end;

Meratakan Nilai Formulir yang Terisi ke dalam Halaman

// Without flattening, FormField[i] := value writes only the /V entry —
// the visible appearance comes from the /AP stream and may not refresh
// GenerateFormAppearances regenerates /AP for every widget, then
// FlattenAllPages bakes every page's annotations and widgets into
// permanent, non-editable content
Pdf1.FileName := 'C:\Application.pdf';
Pdf1.Active := True;
Pdf1.FormField[0] := 'John Smith';
Pdf1.FormField[1] := 'john@example.com';
Pdf1.GenerateFormAppearances;
if Pdf1.FlattenAllPages then
  Pdf1.SaveAs('C:\Application.flat.pdf');
Pdf1.Active := False;

Operasi Gambar

Mengekstrak Gambar

var
  I: Integer;
  Bitmap: TBitmap;
begin
  for I := 0 to Pdf1.BitmapCount - 1 do
  begin
    Bitmap := Pdf1.Bitmap[I];
    try
      Bitmap.SaveToFile('C:\ExtractedImage' + IntToStr(I) + '.bmp');
    finally
      Bitmap.Free;
    end;
  end;
end;

Menambahkan Gambar

var
  JpegStream: TFileStream;
begin
  JpegStream := TFileStream.Create('C:\MyImage.jpg', fmOpenRead or fmShareDenyWrite);
  try
    Pdf1.AddJpegImage(JpegStream, 100, 500, 200, 150);
  finally
    JpegStream.Free;
  end;
  Pdf1.AddPicture(Image1.Picture, 100, 300, 150, 100);
  Pdf1.UpdatePage;
end;

Operasi Anotasi

Bekerja dengan Anotasi

var
  I: Integer;
  Annotation: TPdfAnnotation;
begin
  for I := 0 to Pdf1.AnnotationCount - 1 do
  begin
    Annotation := Pdf1.Annotation[I];
    ShowMessage('Anotasi ' + IntToStr(I) + ': ' + Annotation.ContentsText);
  end;
end;

Operasi Menggambar

Menggambar Bentuk

Pdf1.CreatePath(100, 100, 200, 150, fmAlternate, clYellow, $FF, True, clBlack, $FF, 2.0);
Pdf1.AddPath;

Pdf1.CreatePath(100, 100, fmNone, clBlack, $FF, True, clRed, $FF, 3.0);
Pdf1.LineTo(200, 100);
Pdf1.LineTo(150, 200);
Pdf1.ClosePath;
Pdf1.AddPath;
Pdf1.UpdatePage;

Navigasi dan Bookmark

Bekerja dengan Bookmark

var
  I: Integer;
  Bookmarks: TBookmarks;
begin
  Bookmarks := Pdf1.Bookmarks;
  for I := 0 to Length(Bookmarks) - 1 do
    ShowMessage(Bookmarks[I].Title + ' -> Halaman ' + IntToStr(Bookmarks[I].PageNumber));
end;

Bekerja dengan Tujuan Bernama

var
  I: Integer;
  Destination: TDestination;
begin
  for I := 0 to Pdf1.DestinationCount - 1 do
  begin
    Destination := Pdf1.Destination[I];
    if Destination.Name = 'Chapter1' then
    begin
      Pdf1.PageNumber := Destination.PageNumber;
      Break;
    end;
  end;
end;

Operasi Lampiran

Bekerja dengan Lampiran

var
  I: Integer;
  AttachmentData: TBytes;
  AttachmentName: WString;
  FileStream: TFileStream;
begin
  for I := 0 to Pdf1.AttachmentCount - 1 do
  begin
    AttachmentName := Pdf1.AttachmentName[I];
    AttachmentData := Pdf1.Attachment[I];
    FileStream := TFileStream.Create('C:\Extracted_' + string(AttachmentName), fmCreate);
    try
      if Length(AttachmentData) > 0 then
        FileStream.WriteBuffer(AttachmentData[0], Length(AttachmentData));
    finally
      FileStream.Free;
    end;
  end;
  if Pdf1.CreateAttachment('MyFile.txt') then
    Pdf1.Attachment[Pdf1.AttachmentCount - 1] := TFile.ReadAllBytes('C:\MyFile.txt');
end;

Manajemen Halaman

Operasi Halaman

Pdf1.AddPage(Pdf1.PageCount + 1, 612, 792);
Pdf1.AddPage(Pdf1.PageCount + 1, 595, 842);
Pdf1.AddPage(Pdf1.PageCount + 1, 400, 600);

var
  SourcePdf: TPdf;
begin
  SourcePdf := TPdf.Create(nil);
  try
    SourcePdf.FileName := 'C:\SourceDocument.pdf';
    SourcePdf.Active := True;
    Pdf1.ImportPages(SourcePdf, '1', Pdf1.PageCount + 1);
  finally
    SourcePdf.Free;
  end;
end;

if Pdf1.PageCount > 1 then
  Pdf1.DeletePage(Pdf1.PageCount);

Penyimpanan dan Kompresi

Menyimpan dengan Kompresi

// Create a compressed PDF document
Pdf1.CreateDocument;
Pdf1.Compressed := True; // ensure all streams are FlateDecode compressed (default)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // ukuran A4
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// Save with options
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // incremental save
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // remove security

// Save to a specific PDF version target. The PDF version validator rejects
// any feature introduced after the chosen target (JBIG2Decode, MarkInfo,
// AES encryption, Polygon / Caret / Watermark / Redact annotations, ...)
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
  ShowMessage('Penyimpanan ditolak: dokumen berisi fitur yang lebih baru daripada PDF 1.5');

Membuat Output Arsip PDF/A-1b

// SaveAsPdfA post-processes the base save with an incremental update that
// injects an XMP metadata stream, an sRGB ICC OutputIntent, and an updated
// document catalog so the result is PDF/A-1b conformant. The bundled
// sRGB IEC61966-2.1 profile is used automatically when no custom profile
// is supplied
Pdf1.FileName := 'C:\Report.pdf';
Pdf1.Active := True;
if Pdf1.SaveAsPdfA('C:\Report.pdfa.pdf', pac1b) then
  ShowMessage('Tersimpan sebagai PDF/A-1b archive');

// PDF/A with a custom ICC profile (CMYK printing pipelines for example)
var
  Opts: TPdfASaveOptions;
begin
  Opts := Default(TPdfASaveOptions);
  Opts.Conformance    := pac1b;
  Opts.IccProfileData := TFile.ReadAllBytes('C:\Profiles\Coated_GRACoL_2006.icc');
  Pdf1.SaveAsPdfA('C:\Report.cmyk.pdfa.pdf', Opts);
end;

// In-memory PDF/A output
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    Opts := Default(TPdfASaveOptions);
    Opts.Conformance := pac1b;
    if Pdf1.SaveAsPdfAToStream(Stream, Opts) then
      UploadToArchive(Stream);
  finally
    Stream.Free;
  end;
end;

Memvalidasi Kesesuaian PDF/A

var
  Validation: TPdfAValidationResult;
  Issue: TPdfAValidationIssue;
begin
  Pdf1.FileName := 'C:\Incoming\report.pdf';
  Pdf1.Active   := True;

  // Cheap header-style probe — reads the conformance without iterating
  // the full validator. Result: pacNone, pac1a, pac1b, pac2b, pac3b, or
  // pacUnknown
  case Pdf1.PdfAConformance of
    pac1b: Status.Caption := 'PDF/A-1b';
    pac2b: Status.Caption := 'PDF/A-2b';
    pacNone: Status.Caption := 'Not PDF/A';
  else
    Status.Caption := 'Unknown conformance';
  end;

  // Full validator with per-issue diagnostics
  Validation := Pdf1.ValidatePdfA;
  Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
    Ord(Validation.Conformance)));
  for Issue in Validation.Issues do
    Memo1.Lines.Add('- ' + Issue.Deskripsi);
end;

Memeriksa Berbagai Standar PDF di StandardsLab

Proyek Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab, dan Demo\Lazarus\StandardsLab memuat atau membuat PDF, menjalankan ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR, dan ValidatePdfVT, lalu menampilkan setiap tingkat kesesuaian yang terdeteksi dan jumlah isu dalam satu grid. Tombol simpan mereka memanggil SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR, dan SaveAsPdfVT sehingga alur kerja keluaran penanda terlihat tanpa perlu menulis harness khusus

Menghasilkan Laporan Preflight

Unit FPdfPreflightReport membungkus ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR, dan ValidatePdfVT menjadi satu objek laporan. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport, dan Demo\CBuilder\PreflightReport membiarkan pengguna memilih standar, menghasilkan ringkasan, memeriksa status, prioritas, kategori isu, jumlah kategori, tindakan berikutnya, kode isu, dan tindakan yang direkomendasikan, beralih pratinjau antara text, Markdown, JSON, dan CSV, serta mengekspor laporan TXT, HTML, Markdown, JSON, atau CSV. Sampel GUI Delphi juga menambahkan pratinjau Action Plan yang meringkas standar yang tidak lolos, prioritas, kode isu, dan rekomendasi menjadi daftar perbaikan. Demo\Delphi\PreflightReportCli mengekspos jalur laporan yang sama sebagai alur kerja konsol dengan argumen input / output, password opsional, pilihan format keluaran termasuk plan / actionplan, pemfilteran standar, penyematan laporan tunggal attach=output.pdf opsional, kode keluar failon= untuk gating CI, pemrosesan batch=list.txt, pemindaian batchdir=folder dengan opsi rekursi dan pemrosesan yang diurutkan stabil berdasarkan path, perutean laporan outdir=reports, penamaan laporan yang tahan konflik untuk nama file input yang duplikat, ringkasan TXT / JSON / HTML dengan total status gabungan dan per standar, manifes baris CSV dengan setelan run yang diulang per item, serta mode sampel tanpa argumen untuk skrip dan tugas CI. Laporan yang dihasilkan menyatakan bahwa validator bawaan mencakup pemeriksaan tingkat marker dan file tertentu yang dipilih, sehingga preflight tingkat konten penuh tetap bisa didelegasikan ke mesin validasi khusus

Pemuatan Streaming dan Dokumen Besar

Memuat PDF Secara On-Demand dengan LoadCustomDocument

// LoadCustomDocument lets PDFium pull blocks from any seekable TStream on
// demand. The stream is NOT copied into memory, so multi-GB PDFs, remote
// HTTP body streams, and database-backed sources are all viable without
// an up-front buffer
var
  FileStream: TFileStream;
begin
  // Read-from-file streaming (no in-memory copy)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ ownership transferred — Pdf1 frees FileStream on UnloadDocument
  Pdf1.Active := True;
  ShowMessage('Halaman: ' + IntToStr(Pdf1.PageCount));
end;

// Externally-owned stream (caller frees it after UnloadDocument)
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create('C:\Live.pdf',
    fmOpenRead or fmShareDenyNone);
  try
    Pdf1.LoadCustomDocument(Stream, False);
    Pdf1.Active := True;
    DoWork;
    Pdf1.Active := False;
  finally
    Stream.Free;
  end;
end;

Pembatalan Asinkron dan Rendering Progresif

Token Pembatalan Kooperatif

// FPdfAsync exposes IPdfCancellationToken /
// IPdfCancellationTokenSource for cancel-from-UI semantics that work
// across the long-running export and render paths
uses FPdfAsync;

var
  CancelSource: IPdfCancellationTokenSource;
  CancelToken:  IPdfCancellationToken;
begin
  CancelSource := PdfCancellationTokenSource;
  CancelToken  := CancelSource.Token;

  // ... user clicks Cancel ..
  CancelSource.Cancel;

  // The worker periodically checks CancelToken.IsCancelled, or the render
  // path polls it through PDFium's IFSDK_PAUSE callback
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

Render Halaman Progresif yang Dapat Dibatalkan

// RenderPageProgressive lets a long high-DPI render abort mid-page when
// the user clicks Cancel, instead of blocking the caller until done
var
  Status: TPdfProgressiveStatus;
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.PixelFormat := pf32bit;
    Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI

    Status := Pdf1.RenderPageProgressive(Bitmap, 0, 0, Bitmap.Width,
      Bitmap.Height, CancelToken, ro0, [reAnnotations], clWhite);

    case Status of
      prsDone:      SaveBitmap(Bitmap);
      prsCancelled: Memo1.Lines.Add('Render cancelled — partial result available');
      prsFailed:    Memo1.Lines.Add('PDFium reported FPDF_RENDER_FAILED');
    end;
  finally
    Bitmap.Free;
  end;
end;

Menjalankan Render di Thread Latar Belakang dengan TPdfFuture

// TPdfFuture<T> spins up a worker on a background thread and posts a
// TPdfFutureResult<T> envelope back to the main thread
//
// Important: TPdf serializes render calls per instance, but the worker still
// owns dispatch and document lifetime. Do not mutate a TPdf while a render is
// in flight; batch export usually keeps one TPdf per file
type
  TRenderResult = record
    Bitmap : TBitmap;
    PageNo : Integer;
  end;

var
  Future: TPdfFuture<TRenderResult>;
begin
  Future := TPdfFuture<TRenderResult>.Create(
    function (const ACancelToken: IPdfCancellationToken): TRenderResult
    var
      LocalPdf: TPdf;
    begin
      LocalPdf := TPdf.Create(nil);
      try
        LocalPdf.FileName := 'C:\Huge.pdf';
        LocalPdf.Active   := True;
        LocalPdf.PageNumber := 1;
        Result.Bitmap := LocalPdf.RenderPage(0, 0, 2480, 3508);
        Result.PageNo := 1;
      finally
        LocalPdf.Free;
      end;
    end,
    procedure (const AResult: TPdfFutureResult<TRenderResult>)
    begin
      case AResult.State of
        pfsSuccess:   PaintBitmapToView(AResult.Value.Bitmap);
        pfsCancelled: Log.Add('Cancelled');
        pfsFailed:    Log.Add('Worker failed: ' + AResult.ErrorMessage);
      end;
    end);
end;

Matriks Transformasi

Menyusun Translate / Scale / Rotate / Skew

// FPdfMatrix wraps PDFium's FS_MATRIX so callers can build a transform
// declaratively and hand it to FPDFPageObj_SetMatrix
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // move 1in right, ~2.8in up
    M.Scale(0.5, 0.5);          // half-size stamp
    M.Rotate(15);               // 15-degree CCW rotation
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // post-multiply with another matrix

    // Terapkan ke objek halaman PDFium apa pun (text / path / image / form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

Komposisi Halaman

Mengimpor Sebagian Halaman berdasarkan Indeks

// ImportPagesByIndex copies an explicit zero-based index array from a
// source PDF into this document. InsertAt = 0 inserts before page 1;
// PageCount appends
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // Import pages 0, 2, 4 (one-based: pages 1, 3, 5) from Source
    // and append them at the end of Pdf1
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // Empty array imports every source page
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

Output Komposit N-up

// ImportNPagesToOne returns a brand-new TPdf whose pages are NumX*NumY
// composites of THIS document. OutputWidth/Height are PDF user units
// (1 unit = 1/72 in)
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // 4-up A4 landscape: 842 x 595 pt, 2 columns x 2 rows
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

Menata Ulang Halaman Secara In-Place

// MovePages takes the zero-based indices of the pages to move plus the
// destination index for the first moved page after the move completes
// Other pages shift around the moved block so the document length stays
// the same
//
// Contoh: in a 5-page document with pages numbered [0,1,2,3,4],
// MovePages([2, 3], 0) reorders to [2, 3, 0, 1, 4]
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

Pemberian watermark melalui penggunaan ulang Form XObject halaman

// CreateXObjectFromPage membuat handle Form XObject yang dapat digunakan ulang dari
// any page of any other TPdf. Stamp that handle onto as many pages of
// Atur dokumen ini sesuai kebutuhan Anda — setiap stempel adalah satu objek halaman PDFium
// that can be positioned, scaled, and rotated through the matrix API
uses FPdfMatrix, FPdfView;

var
  WatermarkSource: TPdf;
  XObj:            TPdfXObject;
  PageObj:         FPDF_PAGEOBJECT;
  M:               TPdfMatrix;
  RawMatrix:       FS_MATRIX;
  I:               Integer;
begin
  WatermarkSource := TPdf.Create(nil);
  try
    WatermarkSource.FileName := 'C:\Watermarks\Confidential.pdf';
    WatermarkSource.Active   := True;

    // Wrap page 0 of WatermarkSource as a reusable XObject
    XObj := Pdf1.CreateXObjectFromPage(WatermarkSource, 0);
    try
      for I := 1 to Pdf1.PageCount do
      begin
        Pdf1.PageNumber := I;
        PageObj := Pdf1.InsertFormObjectFromXObject(XObj);
        if PageObj = nil then Continue;

        // Centre the watermark on the page
        M := TPdfMatrix.Create;
        try
          M.Scale(0.5, 0.5);
          M.Translate(0.25 * Pdf1.PageWidth, 0.5 * Pdf1.PageHeight);
          RawMatrix := M.Handle;
          FPDFPageObj_SetMatrix(PageObj, RawMatrix);
        finally
          M.Free;
        end;

        Pdf1.UpdatePage;
      end;
    finally
      XObj.Free; // menutup handle FPDF_XOBJECT
    end;

    Pdf1.SaveAs('C:\Watermarked.pdf');
  finally
    WatermarkSource.Free;
  end;
end;

Proyek Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp, dan Demo\CBuilder\WatermarkStamp mengubah pola ini menjadi alur kerja yang dapat dijalankan: proyek ini membuat PDF target dan PDF stempel yang dapat digunakan ulang, membungkus halaman stempel dengan CreateXObjectFromPage, menyisipkan Form XObject yang sama di setiap halaman target, menerapkan transformasi TPdfMatrix untuk watermark yang dipusatkan dan stempel kanan atas, lalu menambahkan label nomor halaman opsional sebelum menyimpan hasilnya

Penyisipan Gambar (Overload AddImage Langsung)

Menambahkan File Gambar Terdaftar atau TBitmap

// AddImage(FileName) accepts any format registered with the VCL or LCL
// graphics units (BMP, PNG via PngImage, JPG, ...)
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) skips the TPicture intermediary, ideal when the
// bitmap already comes from rendering or batch generation work
var
  Bitmap: TBitmap;
begin
  Bitmap := Pdf1.RenderPage(0, 0, 595, 842);
  try
    Pdf1.PageNumber := 2;
    Pdf1.AddImage(Bitmap, 50, 50, 250, 350);
    Pdf1.UpdatePage;
  finally
    Bitmap.Free;
  end;
end;

Proyek Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF, dan Demo\Lazarus\ImageToPDF mengubahnya menjadi alur kerja batch penuh: pilih beberapa file gambar, tinjau item yang dipilih, buat satu halaman PDF untuk setiap gambar, skalakan setiap gambar ke halaman A4 potret atau lanskap, simpan hasilnya, dan buka PDF yang dihasilkan

Penyorotan Hasil Pencarian di Viewer

Menyorot Setiap Kecocokan di Halaman Saat Ini

var
  MatchCount: Integer;
begin
  // Paint a clYellow mask over every match on the view's current page
  // Beralih ke halaman lain akan menghapus sorotan secara otomatis
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // Programmatically remove the overlay before the page changes
  PdfView1.ClearHighlight;
end;

Tata Letak Viewer dan Warna Halaman

Mode Fit dan Latar Belakang Halaman

// FitMode keeps long documents framed during Resize or page changes
// Setting Zoom directly cancels FitMode (it reverts to pfmNone)
PdfView1.FitMode := pfmFitPage;       // seluruh halaman dalam viewport
// PdfView1.FitMode := pfmFitWidth;   // lebar halaman cocok dengan lebar viewport
// PdfView1.FitMode := pfmActualSize; // 100%

// PageColor decouples the rendered PDF page from the host control's Color
// — typical dark-mode viewer setup keeps the scroll area dark while the
// PDF page stays paper-white
PdfView1.Color     := clBlack;        // scroll area
PdfView1.PageColor := clWhite;        // PDF page background

// Optional drop shadow and page border to lift the page off a dark bg
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

Mengeksplorasi Interaksi Viewer di ViewerInteractionLab

Proyek Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab, dan Demo\Lazarus\ViewerInteractionLab menjaga permukaan interaksi TPdfView modern dalam satu formulir ringkas: membuka atau membuat PDF contoh, mengganti DisplayMode, menerapkan FitMode, mengubah PageColor, memutar halaman, menyorot hasil pencarian halaman saat ini dengan HighlightSearchText, mengaktifkan atau menonaktifkan pemilihan teks oleh pengguna, memeriksa SelectedText, memanggil SelectAll, CopySelectionToClipboard, dan ClearSelection, serta mengamati koordinat DeviceToPage yang diperbarui di status bar saat mouse bergerak di atas halaman

Mencari dan Memilih Teks di SearchAndSelect

Proyek Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect, dan Demo\Lazarus\SearchAndSelect mengubah alur kerja pencarian dan pemilihan di viewer menjadi sampel mandiri. Mereka membuka atau membuat PDF, mencantumkan hasil pencarian seluruh halaman dengan nomor halaman, indeks karakter, dan teks pratinjau, mendukung navigasi Previous / Next dan lompat hasil dengan klik ganda, menyorot kecocokan halaman saat ini dengan HighlightSearchText, mengekspos opsi match-case dan whole-word, mengalihkan AllowUserTextSelection, mempratinjau SelectedText, menjalankan SelectAll, CopySelectionToClipboard, dan ClearSelection dari tombol, serta membiarkan pengguna beralih antara mode tampilan single dan spread

Membaca Preferensi Cetak PDF di PrintPreferences

Proyek Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences, dan Demo\Lazarus\PrintPreferences membaca preferensi cetak penulis sebelum mencetak. Mereka menampilkan PrintCopies, PrintPageRanges, PrintScaling, dan PrintPaperHandling, dapat menyalin jumlah salinan dan rentang yang disediakan penulis ke input cetak, serta menunjukkan bagaimana SetPdfPrintPaperHandlingDevMode memetakan niat duplex PDF ke dalam Windows DEVMODE

Pencetakan PDF Standar di PrintPDF

Proyek Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF, dan Demo\Lazarus\PrintPDF menampilkan alur kerja cetak standar: membuka PDF, meninjau halaman, memilih rentang halaman dan jumlah salinan di dialog cetak platform, menghormati mode collate, merender setiap halaman melalui TPdf.RenderPage, dan menampilkan progres cetak dengan pembatalan. Sampel Delphi juga menerima file PDF yang dijatuhkan untuk pemuatan cepat

Penayangan Multihalaman Cepat di MultiPageViewer

Proyek Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer, dan Demo\Lazarus\MultiPageViewer menguji tampilan kontinu, spread, dan kesadaran sampul dengan navigasi halaman biasa serta alur kerja zoom. Proyek ini juga berguna untuk menguji kecepatan buka-ke-halaman-pertama dan respons rendering pada PDF yang sangat besar: pada pengujian file besar, dokumen 2.x GB yang dapat membuat Acrobat atau Foxit menghabiskan 20-30 detik sebelum tampilan pertama dapat menjadi siap pakai hampir seketika melalui jalur viewer PDFiumPas

Tinjauan Berdampingan di SplitView

Proyek Demo\Delphi\SplitView, Demo\CBuilder\SplitView, dan Demo\Lazarus\SplitView menampilkan tinjauan PDF berdampingan. Sampel C++Builder dan Lazarus berfokus pada perbandingan dua atau tiga panel, pemilihan tampilan aktif, perintah navigasi tersinkron opsional, preset zoom bersama, dan rotasi untuk tampilan yang dipilih atau semua tampilan yang dimuat. Gunakan sampel ini untuk membandingkan dokumen besar sambil menjaga tampilan pertama yang dapat dipakai dan rendering halaman berikutnya tetap responsif; file PDF 2.x GB yang sama yang mungkin membutuhkan 20-30 detik untuk dibuka di Acrobat atau Foxit dapat menjadi dapat dipakai hampir seketika di jalur viewer PDFiumPas

Mengaudit Permukaan Risiko PDF di SecurityAudit

Proyek Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit, dan Demo\Lazarus\SecurityAudit membuka PDF dalam mode baca-saja dan mencantumkan status izin, lampiran tertanam, tindakan JavaScript dokumen, anotasi tautan URI dan Launch, web links, ringkasan tanda tangan, status XFA, ketersediaan helper V8, dan callback fitur yang tidak didukung. Baris ringkasan dan temuan yang sama dapat disimpan atau disalin sebagai laporan audit TXT atau disimpan sebagai JSON terstruktur. Sampel ini ditujukan sebagai panel risiko aplikasi host, bukan deteksi malware

Penanganan Tautan Viewer

Click-to-Follow Aman dengan Daftar Izinkan per Tindakan

// LinkOptions controls automatic handling of the four PDF link action
// types. Defaults enable goto and URI only; launch (run a program) and
// embedded GotoR (jump to another file) stay OFF so a stray click cannot
// run arbitrary code
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // safe defaults

// OnAnnotationLinkClick fires for in-document destinations (page jumps,
// named destinations, action chains). Mark Handled = True to prevent the
// view from auto-following the link
procedure TForm1.PdfView1AnnotationLinkClick(Sender: TObject;
  LinkIndex: Integer; const Action: TPdfAction;
  var Handled: Boolean);
begin
  if Action.Kind = paUri then
  begin
    if MessageDlg('Open ' + Action.Uri + '?', mtConfirmation, [mbYes, mbNo], 0)
      <> mrYes then
      Handled := True;  // suppress auto-handling
  end;
end;

// OnWebLinkClick fires for URI strings discovered by PDFium's web-link
// scanner (auto-detected URLs in the text stream, even without an
// /A <</S/URI...>> annotation)
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
  WebLinkIndex: Integer; const Url: WString;
  var Handled: Boolean);
begin
  Log.Add('Web link clicked: ' + string(Url));
end;

Pengeditan Widget Formulir Viewer

Seleksi dan Undo / Redo

// On the focused AcroForm widget, six new methods drive PDFium's form
// edit history. All six short-circuit safely if no widget is focused or
// the document carries no AcroForm
procedure TForm1.btnSelectAllClick(Sender: TObject);
begin
  PdfView1.SelectAllFormText;
  EditMenuCopy.Caption := 'Copy: ' + string(PdfView1.GetSelectedFormText);
end;

procedure TForm1.btnUndoClick(Sender: TObject);
begin
  btnUndo.Enabled := PdfView1.FormCanUndo;
  btnRedo.Enabled := PdfView1.FormCanRedo;
  if PdfView1.FormCanUndo then
    PdfView1.FormUndo;
end;

procedure TForm1.btnRedoClick(Sender: TObject);
begin
  if PdfView1.FormCanRedo then
    PdfView1.FormRedo;
end;

Mengunci Viewer untuk Kios

Menonaktifkan Gestur Navigasi Halaman

// All three default to True so existing apps upgrade with no behaviour
// change. Flip them False for kiosks, preview panes, or read-only
// embeddings without subclassing TPdfView
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Beranda/End
PdfView1.ChangePageOnMouseScrolling := False; // wheel up/down jumps
PdfView1.AllowUserTextSelection    := False;  // mouse/keyboard text selection

// Ctrl+wheel is reserved for zoom and is skipped by the navigation
// handler regardless of ChangePageOnMouseScrolling.

Panel Samping Thumbnail

Menautkan TPdfThumbnailView ke TPdfView

// TPdfThumbnailView (unit FPdfThumbnail) renders one page thumbnail per
// row in a scrollable side panel
uses FPdfThumbnail;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ThumbView.Pdf              := Pdf1;
  ThumbView.ThumbnailWidth   := 120;
  ThumbView.ThumbnailHeight  := 160;
  ThumbView.SelectionColor   := $00CFA85F;  // warm highlight
  ThumbView.OnPageClick      := ThumbViewPageClick;
end;

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex is zero-based
end;

procedure TForm1.PdfView1PageNumberDiubah(Sender: TObject);
begin
  ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;

Ekspor Batch yang Dapat Dibatalkan Secara Kooperatif

PDF Multi-File ke JPG dengan Progres per File dan per Halaman

// Pattern used by Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport,
// and Demo\Lazarus\BatchExport. One TPdf per file, IPdfCancellationToken
// for cancel-from-UI, TPdfStreamAdapter for buffered file load
uses FPdfAsync;

procedure ExportBatch(const Files: TArray<string>;
  const OutDir: string; DPI: Integer; Quality: Integer;
  const ACancelToken: IPdfCancellationToken);
var
  I, J: Integer;
  Job:  TPdf;
  Bmp:  TBitmap;
  Jpeg: TJPEGImage;
begin
  for I := 0 to High(Files) do
  begin
    if ACancelToken.IsCancelled then Break;
    OverallProgress.Position := I;

    Job := TPdf.Create(nil);
    try
      Job.FileName := Files[I];
      Job.Active   := True;
      PageProgress.Max := Job.PageCount;

      for J := 1 to Job.PageCount do
      begin
        if ACancelToken.IsCancelled then Break;
        PageProgress.Position := J;

        Job.PageNumber := J;
        Bmp := Job.RenderPage(
          0, 0,
          Round(Job.PageWidth  * DPI / 72),
          Round(Job.PageHeight * DPI / 72));
        try
          Jpeg := TJPEGImage.Create;
          try
            Jpeg.CompressionQuality := Quality;
            Jpeg.Assign(Bmp);
            Jpeg.SaveToFile(Format('%s\%s.p%.3d.jpg',
              [OutDir, ExtractFileName(Files[I]), J]));
          finally
            Jpeg.Free;
          end;
        finally
          Bmp.Free;
        end;
      end;
    finally
      Job.Free;
    end;
  end;
end;

Koordinat Viewer dan Hit Testing

Menyesuaikan Halaman Saat Ini dan Memeriksa Teks di Bawah Mouse

procedure FitCurrentPage;
begin
  PdfView1.Zoom := PdfView1.PageZoom[PdfView1.PageNumber];
end;

procedure InspectViewerPoint(X, Y: Integer);
var
  PageNo: Integer;
  PageX, PageY: Double;
  CharIndex: Integer;
  Ch: WString;
begin
  PageNo := PdfView1.PageNumber;
  if not PdfView1.DeviceToPage(X, Y, PageNo, PageX, PageY) then
    Exit;

  CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 8, 8);
  if CharIndex >= 0 then
  begin
    Ch := PdfView1.Text(CharIndex, 1);
    ShowMessage(Format('Page %d, X %.2f, Y %.2f, character %s',
      [PageNo, PageX, PageY, string(Ch)]));
  end;
end;

Penanganan Error

Operasi PDF yang Tangguh

function LoadPdfSafely(const FileName: string): Boolean;
begin
  Result := False;
  if not FileExists(FileName) then
    Exit;
  try
    Pdf1.FileName := FileName;
    Pdf1.Active := True;
    Result := Pdf1.Active;
  except
    Result := False;
  end;
end;

function ExtractTextSafely(PageNo: Integer): WString;
begin
  Result := '';
  if not Pdf1.Active then
    Exit;
  if (PageNo < 1) or (PageNo > Pdf1.PageCount) then
    Exit;
  Pdf1.PageNumber := PageNo;
  Result := Pdf1.Text;
end;

Tagged PDF dan Aksesibilitas

Mendeteksi Tagged PDF dan Menelusuri Pohon Struktur

// IsTagged is a cheap catalog probe — True when /StructTreeRoot exists
// StructureElements materialises the tree into a flat array of
// TPdfStructureElement records with type, title, alternate text, actual
// text, expansion text, language, level, parent index, and child / marked
// content / attribute counts
var
  Elements: TPdfStructureElements;
  I: Integer;
begin
  if not Pdf1.IsTagged then
  begin
    Log.Add('Document is NOT tagged — accessibility tools may struggle.');
    Exit;
  end;
  if Pdf1.Language = '' then
    Log.Add('Tagged PDF without /Lang — fails PDF/UA.');

  Elements := Pdf1.StructureElements;
  for I := 0 to High(Elements) do
    Log.Add(StringOfChar(' ', Elements[I].Level * 2)
      + Elements[I].StructType + ' "' + Elements[I].Title + '"');
end;

Pemilihan Mode Cetak

Menetapkan Mode Cetak EMF / PostScript / Image-Mask

// SetPdfPrintMode is a global function that maps to PDFium's
// FPDF_SetPrintMode. Use the matching TPdfPrintMode value for the
// printer driver in use
SetPdfPrintMode(pmEmf);                   // GDI EMF (default)
// SetPdfPrintMode(pmTextOnly);           // text-only rendering
// SetPdfPrintMode(pmPostScript2);        // PostScript Level 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + image masks
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + Type 42 fonts

Membaca Pengaturan Cetak yang Dimaksudkan Penulis

// PrintCopies, PrintPageRanges, PrintScaling, and PrintPaperHandling are
// the viewer-preference dictionary entries — they encode what the
// document author wanted PDF readers to default to
var
  Ranges: TPrintPageRanges;
  Range: TPrintPageRange;
begin
  ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
  ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
    Ord(Pdf1.PrintScaling)));

  Ranges := Pdf1.PrintPageRanges; // one-based ranges
  for Range in Ranges do
    Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));

  if SetPdfPrintPaperHandlingDevMode(PrinterDevMode, Pdf1.PrintPaperHandling) then
    ApplyPrinterDevMode(PrinterDevMode);
end;

Tindakan JavaScript Dokumen

Mendaftar JavaScript dalam Name-Tree

// Document-level JavaScript is stored in the /Names /JavaScript name
// tree. JavaScriptAction[i] returns one entry; JavaScriptActions returns
// the full array
var
  I: Integer;
  Action: TPdfJavaScriptAction;
begin
  for I := 0 to Pdf1.JavaScriptActionCount - 1 do
  begin
    Action := Pdf1.JavaScriptAction[I];
    Memo1.Lines.Add('Name:   ' + string(Action.Name));
    Memo1.Lines.Add('Script: ' + string(Action.Script));
    Memo1.Lines.Add('---');
  end;
end;

Tips Kinerja

Mengoptimalkan Operasi PDF

procedure ProcessMultiplePages;
var
  I: Integer;
  PageText: WString;
begin
  for I := 1 to Pdf1.PageCount do
  begin
    Pdf1.PageNumber := I;
    PageText := Pdf1.Text;
    ProcessPageText(PageText);
  end;
end;

procedure SetOptimalRenderOptions;
begin
  PdfView1.Options := [reAnnotations, reLcd];
end;

// Zero-copy render path (default since v1.21.0): RenderPage writes
// directly into the destination TBitmap's DIB buffer when callers pass a
// pre-allocated bitmap, avoiding one width * height * 4 bytes copy
procedure RenderIntoCallerBitmap(Bitmap: TBitmap);
begin
  Bitmap.PixelFormat := pf32bit;
  Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI
  Pdf1.RenderPage(Bitmap, 0, 0, Bitmap.Width, Bitmap.Height);
end;