PDFium Component Docs

Przykłady programowania PDFium VCL

Podstawowe operacje na dokumentach

Ładowanie i wyświetlanie dokumentu PDF

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

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

Tworzenie nowego dokumentu PDF

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;

Operacje na tekście

Ekstrakcja tekstu z PDF

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

Wyszukiwanie tekstu

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

Dodawanie tekstu z formatowaniem

Pdf1.PageNumber := 1;
Pdf1.AddText('Document Title', 'Times New Roman', 16, 100, 750, clBlue);
Pdf1.AddText('This is the document content.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;

Inspekcja metadanych znaków i czcionek

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('Object ' + IntToStr(I));
      Memo1.Lines.Add('Base name: ' + Pdf1.FontBaseName[I]);
      Memo1.Lines.Add('Family: ' + Pdf1.FontFamilyName[I]);
      Memo1.Lines.Add('Embedded: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
      Memo1.Lines.Add('Weight: ' + IntToStr(Pdf1.FontWeight[I]));
      Memo1.Lines.Add('Italic angle: ' + 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('Font data bytes: ' + IntToStr(Length(FontBytes)));
    end;

  if Pdf1.CharacterCount > 0 then
  begin
    Memo1.Lines.Add('First character size: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
    Memo1.Lines.Add('First character weight: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
    Memo1.Lines.Add('First character angle: ' + FloatToStr(Pdf1.CharacterAngle[0]));
  end;
end;

Projekty Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties i Demo\Lazarus\FontProperties pokazują te same metadane poprzez TPdf i TPdfView.

Budowanie raportu ekstrakcji treści wielu kategorii

Projekty Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab i Demo\Lazarus\ContentExtractionLab przekształcają ekstrakcję treści w jeden przepływ pracy. Otwierają lub tworzą PDF, umożliwiają użytkownikom wybór metadanych, tekstu stron, obiektów stron, obrazów, załączników, linków, adnotacji, zakładek oraz metryk czcionek / znaków, a następnie generują podsumowanie wraz ze szczegółowym raportem TXT lub JSON. Laboratorium obejmuje również wybór wszystkich / żadnych kategorii, kopiowanie raportu do schowka oraz akcje zapisu źródłowego PDF, dzięki czemu użytkownicy mogą zachować wygenerowany przykładowy PDF obok jego raportu ekstrakcji. Starsze dema ExtractText, ExtractImages, Attachment i FontProperties pozostają skoncentrowanymi przykładami; ExtractText obsługuje opcje zakresu stron i separatora stron do generowania wyjścia tekstowego na stronę w Delphi, C++Builder i Lazarus, podczas gdy ContentExtractionLab jest przekrojowym przykładem inwentaryzacji.

Operacje na formularzach

Praca z polami formularzy

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;

Spłaszczanie wypełnionych wartości formularzy w stronę

// Bez spłaszczania FormField[i] := value zapisuje tylko wpis /V —
// widoczny wygląd pochodzi ze strumienia /AP i może się nie odświeżyć.
// GenerateFormAppearances regeneruje /AP dla każdego widżetu, następnie
// FlattenAllPages wypala adnotacje i widżety każdej strony w
// trwałą, nieedytowalną zawartość.
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;

Operacje na obrazach

Ekstrakcja obrazów

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;

Dodawanie obrazów

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;

Operacje na adnotacjach

Praca z adnotacjami

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

Operacje rysowania

Rysowanie kształtów

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;

Nawigacja i zakładki

Praca z zakładkami

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

Praca z nazwanymi miejscami docelowymi

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;

Operacje na załącznikach

Praca z załącznikami

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;

Zarządzanie stronami

Operacje na stronach

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);

Zapisywanie i kompresja

Zapisywanie z kompresją

// Utwórz skompresowany dokument PDF
Pdf1.CreateDocument;
Pdf1.Compressed := True; // upewnij się, że wszystkie strumienie są skompresowane FlateDecode (domyślnie)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // rozmiar A4
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// Zapisz z opcjami
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // zapis przyrostowy
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // usuń zabezpieczenia

// Zapisz z określonym celem wersji PDF. Walidator wersji PDF odrzuca
// każdą funkcję wprowadzoną po wybranym celu (JBIG2Decode, MarkInfo,
// szyfrowanie AES, adnotacje Polygon / Caret / Watermark / Redact, ...).
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
  ShowMessage('Save rejected: document contains features newer than PDF 1.5');

Tworzenie wyjścia archiwalnego PDF/A-1b

// SaveAsPdfA przetwarza wynik zapisu bazowego poprzez aktualizację przyrostową,
// która wstrzykuje strumień metadanych XMP, OutputIntent sRGB ICC oraz
// zaktualizowany Catalog dokumentu, dzięki czemu wynik jest zgodny z PDF/A-1b.
// Dołączony profil sRGB IEC61966-2.1 jest używany automatycznie, gdy nie podano
// niestandardowego profilu.
Pdf1.FileName := 'C:\Report.pdf';
Pdf1.Active := True;
if Pdf1.SaveAsPdfA('C:\Report.pdfa.pdf', pac1b) then
  ShowMessage('Saved PDF/A-1b archive');

// PDF/A z niestandardowym profilem ICC (np. potoki druku CMYK)
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;

// Wyjście PDF/A w pamięci
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;

Walidacja zgodności PDF/A

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

  // Tania sonda w stylu nagłówka — odczytuje zgodność bez iteracji
  // pełnego walidatora. Wynik: pacNone, pac1a, pac1b, pac2b, pac3b lub
  // 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;

  // Pełny walidator z diagnostyką per problem
  Validation := Pdf1.ValidatePdfA;
  Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
    Ord(Validation.Conformance)));
  for Issue in Validation.Issues do
    Memo1.Lines.Add('- ' + Issue.Description);
end;

Sprawdzanie wielu standardów PDF w StandardsLab

Projekty Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab i Demo\Lazarus\StandardsLab ładują lub tworzą PDF, uruchamiają ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR i ValidatePdfVT, a następnie pokazują każdy wykryty poziom zgodności i liczbę problemów w jednej siatce. Ich przyciski zapisu wywołują SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR i SaveAsPdfVT, dzięki czemu przepływ pracy wyjścia z markerami jest widoczny bez pisania niestandardowego harnessu.

Generowanie raportów preflight

Jednostka FPdfPreflightReport opakowuje ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR i ValidatePdfVT w jeden obiekt raportu. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport i Demo\CBuilder\PreflightReport umożliwiają użytkownikom wybór standardów, generowanie podsumowania, sprawdzanie statusu, priorytetu, kategorii problemów, liczników kategorii, kolejnych akcji, kodów problemów i rekomendowanych działań, przełączanie podglądu między tekstem, Markdown, JSON i CSV oraz eksport raportów TXT, HTML, Markdown, JSON lub CSV. Przykład GUI Delphi również dodaje podgląd Action Plan, który kondensuje niezaliczone standardy, priorytety, kody problemów i rekomendacje w listę kroków naprawczych. Demo\Delphi\PreflightReportCli udostępnia tę samą ścieżkę raportu jako przepływ pracy konsoli z argumentami wejścia / wyjścia, opcjonalnym hasłem, wyborem formatu wyjścia z plan / actionplan, filtrowaniem standardów, opcjonalnym osadzeniem raportu pojedynczego pliku attach=output.pdf, kodami wyjścia bramkującymi CI failon=, przetwarzaniem batch=list.txt, skanowaniem katalogów batchdir=folder z opcjonalną rekursją i stabilnym przetwarzaniem posortowanym po ścieżce, routingiem raportów outdir=reports, bezpiecznymi dla kolizji nazwami raportów dla zduplikowanych nazw plików wejściowych, podsumowaniami TXT / JSON / HTML ze zbiorczymi i standardowymi sumami statusu, manifestami wierszy CSV z ustawieniami uruchomienia powtarzanymi per element oraz trybem przykładowym bez argumentów dla skryptów i zadań CI. Wygenerowany raport stwierdza, że wbudowane walidatory pokrywają kontrole na poziomie markerów i wybrane na poziomie pliku, dzięki czemu pełne sprawdzenie preflight na poziomie treści może zostać delegowane do dedykowanego silnika walidacji.

Ładowanie strumieniowe i duże dokumenty

Ładowanie PDF na żądanie za pomocą LoadCustomDocument

// LoadCustomDocument pozwala PDFium pobierać bloki z dowolnego strumienia
// TStream z możliwością przeszukiwania na żądanie. Strumień NIE jest
// kopiowany do pamięci, więc wielogigabajtowe pliki PDF, zdalne strumienie
// treści HTTP i źródła bazodanowe są wszystkie wykonalne bez wstępnego
// buforowania.
var
  FileStream: TFileStream;
begin
  // Strumieniowe odczytywanie z pliku (bez kopii w pamięci)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ własność przeniesiona — Pdf1 zwalnia FileStream przy UnloadDocument.
  Pdf1.Active := True;
  ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;

// Strumień zarządzany zewnętrznie (wywołujący zwalnia go po 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;

Anulowanie asynchroniczne i renderowanie progresywne

Kooperatywne tokeny anulowania

// FPdfAsync udostępnia IPdfCancellationToken /
// IPdfCancellationTokenSource dla semantyki anulowania z UI, która
// działa na długo wykonywanych ścieżkach eksportu i renderowania.
uses FPdfAsync;

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

  // ... użytkownik klika Anuluj ...
  CancelSource.Cancel;

  // Worker okresowo sprawdza CancelToken.IsCancelled, lub ścieżka
  // renderowania sprawdza go poprzez callback IFSDK_PAUSE PDFium.
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

Anulowalne progresywne renderowanie strony

// RenderPageProgressive pozwala długiemu renderowaniu w wysokim DPI przerwać
// w połowie strony, gdy użytkownik kliknie Anuluj, zamiast blokować wywołującego
// do zakończenia.
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;

Uruchamianie renderowań w wątku tła za pomocą TPdfFuture

// TPdfFuture<T> uruchamia workera w wątku tła i wysyła kopertę
// TPdfFutureResult<T> z powrotem do głównego wątku.
//
// Ważne: TPdf serializuje wywołania renderowania per instancję, ale worker nadal
// posiada dyspozytora i czas życia dokumentu. Nie modyfikuj TPdf, gdy renderowanie
// jest w toku; eksport wsadowy zwykle utrzymuje jeden TPdf na plik.
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;

Macierze transformacji

Komponowanie Translate / Scale / Rotate / Skew

// FPdfMatrix opakowuje FS_MATRIX PDFium, dzięki czemu wywołujący mogą zbudować
// transformację deklaratywnie i przekazać ją do FPDFPageObj_SetMatrix.
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // przesuń o 1 cal w prawo, ~2.8 cala w górę
    M.Scale(0.5, 0.5);          // stempel o połowie rozmiaru
    M.Rotate(15);               // obrót o 15 stopni przeciwnie do ruchu wskazówek zegara
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // post-multiplikacja z inną macierzą

    // Zastosuj do dowolnego obiektu strony PDFium (tekst / ścieżka / obraz / Form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

Kompozycja stron

Import podzbioru stron według indeksu

// ImportPagesByIndex kopiuje jawną tablicę indeksów liczonych od zera z
// źródłowego PDF do tego dokumentu. InsertAt = 0 wstawia przed stroną 1;
// PageCount dołącza na końcu.
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // Importuj strony 0, 2, 4 (liczone od 1: strony 1, 3, 5) ze Source
    // i dołącz je na końcu Pdf1.
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // Pusta tablica importuje każdą stronę źródłową
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

Wyjście kompozytowe N-up

// ImportNPagesToOne zwraca zupełnie nowy TPdf, którego strony są kompozytami
// NumX*NumY TEGO dokumentu. OutputWidth/Height to jednostki użytkownika PDF
// (1 jednostka = 1/72 cala).
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // 4-up A4 poziomo: 842 x 595 pt, 2 kolumny x 2 wiersze
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

Zmiana kolejności stron w miejscu

// MovePages przyjmuje indeksy stron liczone od zera, które mają zostać
// przeniesione, plus indeks docelowy dla pierwszej przeniesionej strony
// po zakończeniu przenoszenia. Inne strony przesuwają się wokół przeniesionego
// bloku, dzięki czemu długość dokumentu pozostaje taka sama.
//
// Przykład: w 5-stronicowym dokumencie ze stronami ponumerowanymi [0,1,2,3,4],
// MovePages([2, 3], 0) zmienia kolejność na [2, 3, 0, 1, 4].
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

Znak wodny poprzez ponowne użycie strony Form XObject

// CreateXObjectFromPage tworzy wielokrotnie używalny uchwyt Form XObject z
// dowolnej strony dowolnego innego TPdf. Stempluj ten uchwyt na dowolnej liczbie
// stron TEGO dokumentu — każdy stempel jest pojedynczym obiektem strony PDFium,
// który może być pozycjonowany, skalowany i obracany poprzez API macierzy.
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;

    // Opakuj stronę 0 WatermarkSource jako wielokrotnie używalny 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;

        // Wycentruj znak wodny na stronie
        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; // zamyka uchwyt FPDF_XOBJECT
    end;

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

Projekty Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp i Demo\CBuilder\WatermarkStamp przekształcają ten wzorzec w uruchamialny przepływ pracy: tworzą docelowy PDF i wielokrotnie używalny PDF stempla, opakowują stronę stempla za pomocą CreateXObjectFromPage, wstawiają ten sam Form XObject na każdą docelową stronę, stosują transformacje TPdfMatrix dla wycentrowanych znaków wodnych i stempli w prawym górnym rogu oraz dodają opcjonalne etykiety numerów stron przed zapisaniem wyniku.

Wstawianie obrazów (bezpośrednie przeciążenia AddImage)

Dodawanie zarejestrowanego pliku obrazu lub TBitmap

// AddImage(FileName) akceptuje każdy format zarejestrowany w jednostkach
// graficznych VCL lub LCL (BMP, PNG przez PngImage, JPG, ...).
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) pomija pośrednika TPicture, idealne gdy bitmapa
// pochodzi już z renderowania lub generowania wsadowego.
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;

Projekty Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF i Demo\Lazarus\ImageToPDF przekształcają to w pełny przepływ pracy wsadowy: wybierz wiele plików obrazów, podgląd wybranego elementu, utwórz jedną stronę PDF na obraz, przeskaluj każdy obraz do strony A4 pionowej lub poziomej, zapisz wynik i otwórz wygenerowany PDF.

Podświetlanie wyników wyszukiwania w przeglądarce

Podświetl każde dopasowanie na bieżącej stronie

var
  MatchCount: Integer;
begin
  // Nałóż maskę clYellow nad każdym dopasowaniem na bieżącej stronie widoku.
  // Przejście na inną stronę usuwa podświetlenie automatycznie.
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // Programowo usuń nakładkę przed zmianą strony
  PdfView1.ClearHighlight;
end;

Układ przeglądarki i kolor strony

Tryb dopasowania i tło strony

// FitMode utrzymuje długie dokumenty w ramach podczas zmiany rozmiaru lub zmiany strony.
// Ustawienie Zoom bezpośrednio anuluje FitMode (powraca do pfmNone).
PdfView1.FitMode := pfmFitPage;       // cała strona w obszarze wyświetlania
// PdfView1.FitMode := pfmFitWidth;   // szerokość strony pasuje do szerokości obszaru wyświetlania
// PdfView1.FitMode := pfmActualSize; // 100%

// PageColor oddziela renderowaną stronę PDF od koloru kontrolki hosta
// — typowa konfiguracja przeglądarki w trybie ciemnym utrzymuje obszar
// przewijania ciemny, podczas gdy strona PDF pozostaje papierowo biała.
PdfView1.Color     := clBlack;        // obszar przewijania
PdfView1.PageColor := clWhite;        // tło strony PDF

// Opcjonalny cień i obramowanie strony, aby unieść stronę nad ciemnym tłem
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

Eksplorowanie interakcji przeglądarki w ViewerInteractionLab

Projekty Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab i Demo\Lazarus\ViewerInteractionLab utrzymują nowoczesną powierzchnię interakcji TPdfView w jednym kompaktowym formularzu: otwórz lub utwórz przykładowy PDF, przełącz DisplayMode, zastosuj FitMode, zmień PageColor, obróć strony, podświetl dopasowania wyszukiwania bieżącej strony za pomocą HighlightSearchText, włącz lub wyłącz zaznaczanie tekstu przez użytkownika, sprawdź SelectedText, wywołaj SelectAll, CopySelectionToClipboard i ClearSelection oraz obserwuj współrzędne DeviceToPage aktualizujące się w pasku statusu, gdy mysz porusza się nad stroną.

Wyszukiwanie i zaznaczanie tekstu w SearchAndSelect

Projekty Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect i Demo\Lazarus\SearchAndSelect przekształcają przepływ pracy wyszukiwania i zaznaczania w samodzielne próbki. Otwierają lub tworzą PDF, wymieniają wyniki wyszukiwania ze wszystkich stron z numerem strony, indeksem znaku i tekstem podglądu, obsługują nawigację Poprzedni / Następny i podwójne kliknięcie wyniku, podświetlają dopasowania bieżącej strony za pomocą HighlightSearchText, udostępniają opcje dopasowania wielkości liter i całego słowa, przełączają AllowUserTextSelection, podglądają SelectedText, sterują SelectAll, CopySelectionToClipboard i ClearSelection z przycisków oraz pozwalają użytkownikom przełączać się między pojedynczymi a rozkładówkowymi trybami wyświetlania.

Odczyt preferencji druku PDF w PrintPreferences

Projekty Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences i Demo\Lazarus\PrintPreferences odczytują preferencje druku autora przed drukowaniem. Wyświetlają PrintCopies, PrintPageRanges, PrintScaling i PrintPaperHandling, mogą kopiować podane przez autora kopie i zakresy do wejść druku oraz pokazują, jak SetPdfPrintPaperHandlingDevMode mapuje intencję duplex PDF do Windows DEVMODE.

Standardowe drukowanie PDF w PrintPDF

Projekty Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF i Demo\Lazarus\PrintPDF pokazują standardowy przepływ pracy druku: otwórz PDF, podgląd stron, wybierz zakresy stron i kopie w platformowym oknie dialogowym druku, uwzględnij tryb sortowania, renderuj każdą stronę poprzez TPdf.RenderPage i udostępnij postęp druku z anulowaniem. Przykład Delphi akceptuje również upuszczone pliki PDF dla szybkiego ładowania.

Szybkie wielostronicowe przeglądanie w MultiPageViewer

Projekty Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer i Demo\Lazarus\MultiPageViewer ćwiczą przeglądanie ciągłe, rozkładówkowe i z okładką wraz z regularnymi przepływami pracy nawigacji po stronach i powiększenia. Są również użyteczne do testowania szybkości otwierania pierwszej strony i responsywności renderowania na bardzo dużych plikach PDF: w testach dużych plików dokumenty o rozmiarze 2.x GB, które mogą sprawić, że Acrobat lub Foxit spędzą 20-30 sekund przed pierwszym widokiem, mogą stać się użyteczne niemal natychmiast poprzez ścieżkę przeglądarki PDFiumPas.

Przegląd obok siebie w SplitView

Projekty Demo\Delphi\SplitView, Demo\CBuilder\SplitView i Demo\Lazarus\SplitView pokazują przegląd PDF obok siebie. Próbki C++Builder i Lazarus koncentrują się na porównaniu dwóch lub trzech paneli, wyborze aktywnego widoku, opcjonalnie synchronizowanych poleceniach nawigacji, współdzielonych presetach powiększenia i obrocie dla wybranego widoku lub wszystkich załadowanych widoków. Użyj tych próbek do porównywania dużych dokumentów przy zachowaniu pierwszego użytecznego widoku i responsywnego renderowania kolejnych stron; te same pliki PDF o rozmiarze 2.x GB, których otwarcie może zająć Acrobatowi lub Foxitowi 20-30 sekund, mogą stać się użyteczne niemal natychmiast na ścieżce przeglądarki PDFiumPas.

Audyt powierzchni ryzyka PDF w SecurityAudit

Projekty Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit i Demo\Lazarus\SecurityAudit otwierają PDF tylko do odczytu i wymieniają stan uprawnień, osadzone załączniki, akcje JavaScript dokumentu, adnotacje linków URI i Launch, linki webowe, podsumowania podpisów, stan XFA, dostępność pomocników V8 oraz callbacki nieobsługiwanych funkcji. To samo podsumowanie i wiersze wyników można zapisać lub skopiować jako raport audytu TXT lub zapisać jako ustrukturyzowany JSON. Są przeznaczone jako panele ryzyka aplikacji hosta, nie jako wykrywanie złośliwego oprogramowania.

Obsługa linków w przeglądarce

Bezpieczne kliknięcie-aby-podążyć z listami dozwolonych per akcja

// LinkOptions kontroluje automatyczną obsługę czterech typów akcji linków PDF.
// Domyślnie włącza tylko goto i URI; launch (uruchom program) i embedded GotoR
// (przejdź do innego pliku) pozostają WYŁĄCZONE, aby przypadkowe kliknięcie
// nie mogło uruchomić dowolnego kodu.
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // bezpieczne ustawienia domyślne

// OnAnnotationLinkClick uruchamia się dla miejsc docelowych w dokumencie
// (przejścia do stron, nazwane miejsca docelowe, łańcuchy akcji). Ustaw
// Handled = True, aby zapobiec automatycznemu podążaniu za linkiem.
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;  // wyciszanie automatycznej obsługi
  end;
end;

// OnWebLinkClick uruchamia się dla ciągów URI odkrytych przez skaner
// linków webowych PDFium (automatycznie wykryte URL w strumieniu tekstu,
// nawet bez adnotacji /A <</S/URI...>>).
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
  WebLinkIndex: Integer; const Url: WString;
  var Handled: Boolean);
begin
  Log.Add('Web link clicked: ' + string(Url));
end;

Edycja widżetów formularzy w przeglądarce

Zaznaczanie i Undo / Redo

// Na sfokusowanym widżecie AcroForm sześć nowych metod steruje historią
// edycji formularzy PDFium. Wszystkie sześć bezpiecznie zwraca, jeśli żaden
// widżet nie jest sfokusowany lub dokument nie zawiera 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;

Blokowanie przeglądarki dla kiosków

Wyłącz gesty nawigacji po stronach

// Wszystkie trzy domyślnie ustawione na True, dzięki czemu istniejące aplikacje
// aktualizują się bez zmiany zachowania. Przełącz je na False dla kiosków,
// paneli podglądu lub osadzeń tylko do odczytu bez tworzenia podklasy TPdfView.
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // skoki kółkiem w górę/dół
PdfView1.AllowUserTextSelection    := False;  // zaznaczanie tekstu myszą/klawiaturą

// Ctrl+kółko jest zarezerwowane dla powiększenia i jest pomijane przez
// moduł nawigacji niezależnie od ChangePageOnMouseScrolling.

Boczny panel miniatur

Powiązanie TPdfThumbnailView z TPdfView

// TPdfThumbnailView (jednostka FPdfThumbnail) renderuje jedną miniaturę strony
// w każdym wierszu w przewijalnym panelu bocznym.
uses FPdfThumbnail;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ThumbView.Pdf              := Pdf1;
  ThumbView.ThumbnailWidth   := 120;
  ThumbView.ThumbnailHeight  := 160;
  ThumbView.SelectionColor   := $00CFA85F;  // ciepłe podświetlenie
  ThumbView.OnPageClick      := ThumbViewPageClick;
end;

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex jest liczony od zera
end;

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

Kooperatywnie anulowalny eksport wsadowy

Wieloplikowy PDF do JPG z postępem per plik i per strona

// Wzorzec używany przez Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport
// i Demo\Lazarus\BatchExport. Jeden TPdf na plik, IPdfCancellationToken
// dla anulowania z UI, TPdfStreamAdapter dla buforowanego ładowania pliku.
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;

Współrzędne przeglądarki i testowanie trafień

Dopasuj bieżącą stronę i sprawdź tekst pod kursorem myszy

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;

Obsługa błędów

Niezawodne operacje na PDF

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 i dostępność

Wykryj Tagged PDF i przejdź po drzewie struktury

// IsTagged jest tanim sondowaniem Catalog — True, gdy istnieje /StructTreeRoot.
// StructureElements materializuje drzewo w płaską tablicę rekordów
// TPdfStructureElement z typem, tytułem, tekstem alternatywnym, tekstem
// rzeczywistym, tekstem rozwinięcia, językiem, poziomem, indeksem rodzica
// oraz liczbą dzieci / marked content / atrybutów.
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;

Wybór trybu druku

Ustawienie trybu druku EMF / PostScript / image-mask

// SetPdfPrintMode jest funkcją globalną, która mapuje do
// FPDF_SetPrintMode PDFium. Użyj pasującej wartości TPdfPrintMode dla
// używanego sterownika drukarki.
SetPdfPrintMode(pmEmf);                   // GDI EMF (domyślnie)
// SetPdfPrintMode(pmTextOnly);           // renderowanie tylko tekstu
// SetPdfPrintMode(pmPostScript2);        // PostScript Level 2
// SetPdfPrintMode(pmPostScript3);        // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + maski obrazów
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + czcionki Type 42

Odczyt ustawień druku zamierzonych przez autora

// PrintCopies, PrintPageRanges, PrintScaling i PrintPaperHandling to
// wpisy słownika viewer-preference — kodują to, czego autor dokumentu
// chciał, aby czytniki PDF używały domyślnie.
var
  Ranges: TPrintPageRanges;
  Range: TPrintPageRange;
begin
  ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
  ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
    Ord(Pdf1.PrintScaling)));

  Ranges := Pdf1.PrintPageRanges; // zakresy liczone od 1
  for Range in Ranges do
    Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));

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

Akcje JavaScript dokumentu

Wyliczanie JavaScript drzewa nazw

// JavaScript na poziomie dokumentu jest przechowywany w drzewie nazw
// /Names /JavaScript. JavaScriptAction[i] zwraca jeden wpis;
// JavaScriptActions zwraca pełną tablicę.
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;

Wskazówki dotyczące wydajności

Optymalizacja operacji na 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;

// Ścieżka renderowania bez kopiowania (domyślnie od v1.21.0): RenderPage zapisuje
// bezpośrednio do bufora DIB docelowej TBitmap, gdy wywołujący przekazują
// wstępnie zaalokowaną bitmapę, unikając jednej kopii o rozmiarze szerokość * wysokość * 4 bajty.
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;