PDFium Component Docs

ตัวอย่างการเขียนโปรแกรม PDFium VCL

การทำงานพื้นฐานกับเอกสาร

การโหลดและแสดงเอกสาร 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;

การสร้างเอกสาร 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;

การทำงานกับข้อความ

การดึงข้อความจาก 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;

การค้นหาข้อความ

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;

การเพิ่มข้อความพร้อมการจัดรูปแบบ

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;

การตรวจสอบเมทาดาทาของอักขระและฟอนต์

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;

โปรเจ็กต์ Demo\Delphi\Fontคุณสมบัติ Demo\CBuilder\Fontคุณสมบัติ และ Demo\Lazarus\Fontคุณสมบัติ แสดงเมทาดาทาเดียวกันผ่านทั้ง TPdf และ TPdfView

การสร้างรายงานการดึงเนื้อหาแบบหลายหมวดหมู่

โปรเจ็กต์ Demo\Delphi\ContentExtractionLab Demo\CBuilder\ContentExtractionLab และ Demo\Lazarus\ContentExtractionLab เปลี่ยนการดึงเนื้อหาให้เป็นเวิร์กโฟลว์เดียว พวกมันเปิดหรือสร้าง PDF ให้ผู้ใช้เลือก metadata ข้อความบนหน้า อ็อบเจ็กต์หน้า รูปภาพ ไฟล์แนบ ลิงก์ คำอธิบายประกอบ ที่คั่นหน้า และเมตริกฟอนต์ / อักขระ จากนั้นสร้างทั้งสรุปและรายงาน TXT หรือ JSON แบบละเอียด lab ยังมีการเลือกหมวดหมู่แบบ all / none การคัดลอกรายงานไปยังคลิปบอร์ด และการบันทึก source-PDF เพื่อให้ผู้ใช้เก็บ PDF ตัวอย่างที่สร้างขึ้นไว้ข้างรายงานการดึงเนื้อหาได้ เดโมรุ่นเก่า ExtractText, ExtractImages, Attachment, และ FontProperties ยังคงเป็นตัวอย่างที่มุ่งเน้นเฉพาะด้าน; ExtractText รองรับตัวเลือกช่วงหน้าและตัวแบ่งหน้าเพื่อสร้างผลลัพธ์ข้อความรายหน้าครอบคลุมทั้ง Delphi, C++Builder, และ Lazarus ในขณะที่ ContentExtractionLab เป็นตัวอย่างการสำรวจที่ครอบคลุมข้ามเรื่อง

การทำงานกับฟอร์ม

การทำงานกับฟิลด์ฟอร์ม

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;

ทำให้ค่าฟอร์มที่กรอกแล้วแบนลงบนหน้า

// ถ้าไม่ flatten, FormField[i] := value จะเขียนเฉพาะรายการ /V เท่านั้น —
// รูปลักษณ์ที่เห็นได้มาจากสตรีม /AP และอาจไม่รีเฟรช
// GenerateFormAppearances จะสร้าง /AP ใหม่ให้ทุก widget จากนั้น
// FlattenAllPages จะฝังคำอธิบายประกอบและ widget ของทุกหน้าให้กลายเป็น
// เนื้อหาถาวรที่แก้ไขไม่ได้
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;

การทำงานกับรูปภาพ

การดึงรูปภาพ

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;

การเพิ่มรูปภาพ

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;

การทำงานกับคำอธิบายประกอบ

การทำงานกับคำอธิบายประกอบ

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;

การทำงานกับการวาด

การวาดรูปทรง

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;

การนำทางและที่คั่นหน้า

การทำงานกับที่คั่นหน้า

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;

การทำงานกับปลายทางที่ตั้งชื่อไว้

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;

การทำงานกับไฟล์แนบ

การทำงานกับไฟล์แนบ

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;

การจัดการหน้า

การทำงานกับหน้า

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

การบันทึกและการบีบอัด

การบันทึกพร้อมการบีบอัด

// สร้างเอกสาร PDF ที่บีบอัด
Pdf1.CreateDocument;
Pdf1.Compressed := True; // ตรวจให้แน่ใจว่าสตรีมทั้งหมดถูกบีบอัดด้วย FlateDecode (ค่าเริ่มต้น)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // ขนาด A4
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;

// บันทึกพร้อมตัวเลือก
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // บันทึกแบบเพิ่มทีละส่วน
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // ลบการรักษาความปลอดภัย

// บันทึกไปยังเป้าหมาย PDF เวอร์ชันที่ระบุ ตัวตรวจสอบเวอร์ชัน PDF จะปฏิเสธ
// ฟีเจอร์ใด ๆ ที่ถูกเพิ่มหลังจากเป้าหมายที่เลือกไว้ (JBIG2Decode, MarkInfo,
// การเข้ารหัส AES, คำอธิบายประกอบ 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');

การสร้างผลลัพธ์เก็บถาวร PDF/A-1b

// SaveAsPdfA จะประมวลผลการบันทึกฐานซ้ำด้วย incremental update ที่
// ใส่สตรีม XMP metadata, sRGB ICC OutputIntent และ document catalog
// ที่อัปเดตแล้วเพื่อให้ผลลัพธ์สอดคล้องกับ PDF/A-1b โปรไฟล์
// sRGB IEC61966-2.1 ที่แนบมาจะถูกใช้โดยอัตโนมัติเมื่อไม่ได้ระบุโปรไฟล์เอง
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 พร้อมโปรไฟล์ ICC แบบกำหนดเอง (เช่น ไปป์ไลน์การพิมพ์ 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;

// เอาต์พุต PDF/A ในหน่วยความจำ
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;

การตรวจสอบความสอดคล้องของ PDF/A

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

  // ตรวจแบบเบา ๆ ในระดับส่วนหัว — อ่านความสอดคล้องโดยไม่ต้องวนผ่าน
  // ตัวตรวจสอบเต็ม ผลลัพธ์: pacNone, pac1a, pac1b, pac2b, pac3b หรือ
  // 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;

  // ตัวตรวจสอบเต็มพร้อมการวินิจฉัยรายปัญหา
  Validation := Pdf1.ValidatePdfA;
  Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
    Ord(Validation.Conformance)));
  for Issue in Validation.Issues do
    Memo1.Lines.Add('- ' + Issue.คำอธิบาย);
end;

การตรวจสอบมาตรฐาน PDF หลายประเภทใน StandardsLab

โปรเจ็กต์ Demo\Delphi\StandardsLab Demo\CBuilder\StandardsLab และ Demo\Lazarus\StandardsLab โหลดหรือสร้าง PDF รัน ValidatePdfA ValidatePdfUa ValidatePdfE ValidatePdfX ValidatePdfR และ ValidatePdfVT จากนั้นแสดงระดับความสอดคล้องและจำนวนปัญหาที่ตรวจพบแต่ละรายการในกริดเดียว ปุ่มบันทึกของพวกมันเรียก SaveAsPdfA SaveAsPdfUa SaveAsPdfE SaveAsPdfX SaveAsPdfR และ SaveAsPdfVT เพื่อให้เวิร์กโฟลว์ marker-output มองเห็นได้โดยไม่ต้องเขียน harness เอง

การสร้างรายงาน Preflight

หน่วย FPdfPreflightReport ห่อ ValidatePdfA ValidatePdfUa ValidatePdfE ValidatePdfX ValidatePdfR และ ValidatePdfVT ให้กลายเป็นอ็อบเจ็กต์รายงานเดียว Demo\Delphi\PreflightReport Demo\Lazarus\PreflightReport และ Demo\CBuilder\PreflightReport ให้ผู้ใช้เลือกมาตรฐาน สร้างสรุป ตรวจสอบสถานะ ลำดับความสำคัญ หมวดหมู่ปัญหา จำนวนตามหมวด การกระทำถัดไป รหัสปัญหา และการกระทำที่แนะนำ สลับพรีวิวระหว่าง text Markdown JSON และ CSV และส่งออกรายงาน TXT HTML Markdown JSON หรือ CSV ตัวอย่าง GUI ของ Delphi ยังเพิ่มพรีวิว Action Plan ที่ย่อมาตรฐานที่ไม่ผ่าน ลำดับความสำคัญ รหัสปัญหา และคำแนะนำให้เป็นเช็กลิสต์การแก้ไข Demo\Delphi\PreflightReportCli เปิดเผยเส้นทางรายงานเดียวกันเป็นเวิร์กโฟลว์คอนโซลพร้อมอาร์กิวเมนต์ input / output รหัสผ่านแบบเลือกได้ การเลือกรูปแบบผลลัพธ์รวมถึง plan / actionplan การกรองมาตรฐาน การฝังรายงาน attach=output.pdf แบบไฟล์เดียวแบบเลือกได้ รหัสออกสำหรับ CI ผ่าน failon= การประมวลผล batch=list.txt การสแกนไดเรกทอรี batchdir=folder พร้อมการ recurse แบบเลือกได้และการเรียงเส้นทางอย่างเสถียร การกำหนดเส้นทางรายงาน outdir=reports ชื่อรายงานแบบปลอดการชนกันสำหรับชื่อไฟล์อินพุตที่ซ้ำกัน สรุป TXT / JSON / HTML ที่มีผลรวมไฟล์และสถานะมาตรฐาน manifest แถว CSV ที่ทำซ้ำการตั้งค่าการรันในแต่ละรายการ และโหมดตัวอย่างแบบไม่มีอาร์กิวเมนต์สำหรับสคริปต์และงาน CI รายงานที่สร้างขึ้นระบุว่าตัวตรวจสอบในตัวครอบคลุมการตรวจระดับ marker และระดับไฟล์ที่เลือกไว้ จึงยังสามารถมอบ preflight ระดับเนื้อหาเต็มให้เอนจินตรวจสอบเฉพาะทางได้

การโหลดแบบสตรีมและเอกสารขนาดใหญ่

การโหลด PDF ตามต้องการด้วย LoadCustomDocument

// LoadCustomDocument ให้ PDFium ดึงบล็อกจาก TStream ที่ seek ได้ทุกชนิด
// ตามต้องการ สตรีมจะไม่ถูกคัดลอกเข้า memory ดังนั้น PDF ขนาดหลาย GB,
// สตรีม body ของ HTTP ระยะไกล และแหล่งข้อมูลจากฐานข้อมูลจึงใช้งานได้
// โดยไม่ต้องมีบัฟเฟอร์ล่วงหน้า
var
  FileStream: TFileStream;
begin
  // สตรีมอ่านจากไฟล์ (ไม่คัดลอกเข้า memory)
  FileStream := TFileStream.Create('C:\Huge.pdf',
    fmOpenRead or fmShareDenyWrite);
  Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
  // ^^ โอนความเป็นเจ้าของแล้ว — Pdf1 จะปล่อย FileStream เมื่อ UnloadDocument
  Pdf1.Active := True;
  ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;

// สตรีมที่ผู้เรียกเป็นเจ้าของ (ผู้เรียกเป็นผู้ปล่อยหลัง 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;

การยกเลิกแบบอะซิงโครนัสและการเรนเดอร์แบบก้าวหน้า

โทเค็นการยกเลิกแบบร่วมมือ

// FPdfAsync เปิดเผย IPdfCancellationToken /
// IPdfCancellationTokenSource สำหรับความหมายของการยกเลิกจาก UI
// ที่ใช้งานได้กับเส้นทาง export และ render ที่ทำงานนาน
uses FPdfAsync;

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

  // ... ผู้ใช้คลิก Cancel ...
  CancelSource.Cancel;

  // worker จะตรวจ CancelToken.IsCancelled เป็นระยะ หรือเส้นทาง render
  // จะโพลผ่าน callback IFSDK_PAUSE ของ PDFium
  if CancelToken.IsCancelled then
    Memo1.Lines.Add('Cancelled by user');
end;

การเรนเดอร์หน้าก้าวหน้าที่สามารถยกเลิกได้

// RenderPageProgressive ทำให้การ render ความละเอียดสูงที่ยาวนานยกเลิก
// กลางหน้าได้เมื่อผู้ใช้คลิก Cancel แทนที่จะบล็อกผู้เรียกจนเสร็จ
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;

การรันการเรนเดอร์บนเธรดพื้นหลังด้วย TPdfFuture

// TPdfFuture<T> สร้าง worker บนเธรดพื้นหลังและส่งชุดผลลัพธ์
// TPdfFutureResult<T> กลับไปยังเธรดหลัก
//
// สำคัญ: TPdf จะ serialize การเรียก render แยกตามแต่ละ instance แต่ worker
// ยังเป็นเจ้าของการส่งงานและอายุเอกสาร อย่าแก้ไข TPdf ระหว่างที่มี
// render อยู่ระหว่างทาง; การส่งออกแบบแบตช์มักเก็บ TPdf หนึ่งตัวต่อหนึ่งไฟล์
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;

เมทริกซ์การแปลง

การประกอบการเลื่อน / ย่อขยาย / หมุน / เอียง

// FPdfMatrix ห่อ FS_MATRIX ของ PDFium เพื่อให้ผู้เรียกสร้าง transform
// แบบประกาศเชิงโครงสร้างแล้วส่งต่อให้ FPDFPageObj_SetMatrix
uses FPdfMatrix, FPdfView;

var
  M: TPdfMatrix;
  RawMatrix: FS_MATRIX;
  PageObj: FPDF_PAGEOBJECT;
begin
  M := TPdfMatrix.Create;
  try
    M.Translate(72, 200);       // เลื่อน 1 นิ้วไปทางขวา ขึ้นไปประมาณ 2.8 นิ้ว
    M.Scale(0.5, 0.5);          // ตราประทับขนาดครึ่งหนึ่ง
    M.Rotate(15);               // หมุนทวนเข็มนาฬิกา 15 องศา
    // M.HorizontalFlip;
    // M.VerticalFlip;
    // M.CentralFlip;
    // M.Skew(10, 5);
    // M.Multiply(Other);       // คูณต่อท้ายด้วย matrix อีกตัว

    // ใช้กับ page object ของ PDFium ได้ทุกชนิด (text / path / image / form XObject)
    RawMatrix := M.Handle;
    FPDFPageObj_SetMatrix(PageObj, RawMatrix);
  finally
    M.Free;
  end;
end;

การประกอบหน้า

การนำเข้าชุดย่อยของหน้าตามดัชนี

// ImportPagesByIndex คัดลอกอาร์เรย์ดัชนีแบบ zero-based ที่ระบุไว้อย่าง
// ชัดเจนจาก source PDF มายังเอกสารนี้ InsertAt = 0 จะแทรกก่อนหน้า 1;
// PageCount จะต่อท้าย
var
  Source: TPdf;
begin
  Source := TPdf.Create(nil);
  try
    Source.FileName := 'C:\Report.pdf';
    Source.Active   := True;

    // นำเข้าหน้า 0, 2, 4 (one-based: หน้า 1, 3, 5) จาก Source
    // แล้วต่อท้ายไว้ที่ท้าย Pdf1
    Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);

    // ถ้าใช้อาร์เรย์ว่างจะนำเข้าทุกหน้าของ source
    // Pdf1.ImportPagesByIndex(Source, [], 0);
  finally
    Source.Free;
  end;
end;

ผลลัพธ์แบบ N-up รวมหน้า

// ImportNPagesToOne คืน TPdf ใหม่เอี่ยมที่หน้าทั้งหมดเป็นงานประกอบ
// NumX*NumY จากเอกสารนี้ OutputWidth/Height เป็นหน่วยผู้ใช้ของ PDF
// (1 หน่วย = 1/72 นิ้ว)
var
  Composite: TPdf;
begin
  Pdf1.FileName := 'C:\Slides.pdf';
  Pdf1.Active   := True;
  // A4 แนวนอนแบบ 4-up: 842 x 595 pt, 2 คอลัมน์ x 2 แถว
  Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
  try
    if Composite <> nil then
      Composite.SaveAs('C:\Slides_4up.pdf');
  finally
    Composite.Free;
  end;
end;

การเรียงลำดับหน้าใหม่ภายในที่เดิม

// MovePages รับดัชนีแบบ zero-based ของหน้าที่จะย้ายรวมถึงดัชนีปลายทาง
// ของหน้าที่ถูกย้ายหน้าแรกหลังจากย้ายเสร็จ หน้าที่เหลือจะเลื่อนไปรอบ
// บล็อกที่ย้าย ทำให้ความยาวเอกสารเท่าเดิม
//
// ตัวอย่าง: ในเอกสาร 5 หน้า ที่หน้าถูกนับเป็น [0,1,2,3,4],
// MovePages([2, 3], 0) จะเรียงใหม่เป็น [2, 3, 0, 1, 4]
if not Pdf1.MovePages([2, 3], 0) then
  ShowMessage('Invalid indices passed to MovePages');

การใส่วอเตอร์มาร์กผ่านการใช้หน้า Form XObject ซ้ำ

// CreateXObjectFromPage สร้าง handle Form XObject ที่นำกลับมาใช้ซ้ำได้จาก
// หน้าของ TPdf อื่นใดก็ได้ นำ handle นั้นไปประทับลงบนหน้าในเอกสารนี้ได้
// ตามต้องการ — ตราประทับแต่ละชิ้นเป็น page object ของ PDFium หนึ่งชิ้น
// ที่สามารถจัดตำแหน่ง ปรับสเกล และหมุนผ่าน 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;

    // ห่อหน้า 0 ของ WatermarkSource เป็น 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;

        // จัดวอเตอร์มาร์กให้อยู่กึ่งกลางหน้า
        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; // ปิด handle FPDF_XOBJECT
    end;

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

โปรเจ็กต์ Demo\Delphi\WatermarkStamp Demo\Lazarus\WatermarkStamp และ Demo\CBuilder\WatermarkStamp เปลี่ยนรูปแบบนี้ให้เป็นเวิร์กโฟลว์ที่รันได้ พวกมันสร้าง PDF เป้าหมายและ PDF ตราประทับที่นำกลับมาใช้ได้ ห่อหน้าตราประทับด้วย CreateXObjectFromPage แทรก Form XObject เดียวกันลงบนทุกหน้าเป้าหมาย ใช้การแปลง TPdfMatrix สำหรับวอเตอร์มาร์กกลางหน้าและตราประทับมุมขวาบน และเพิ่มป้ายเลขหน้าตามต้องการก่อนบันทึกผลลัพธ์

การแทรกรูปภาพ (AddImage แบบโอเวอร์โหลดตรง)

การเพิ่มไฟล์รูปภาพที่ลงทะเบียนไว้หรือ TBitmap

// AddImage(FileName) รองรับทุกรูปแบบที่ลงทะเบียนไว้กับ graphics units
// ของ VCL หรือ LCL (BMP, PNG ผ่าน PngImage, JPG, ...)
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);

// AddImage(TBitmap) ข้ามตัวกลาง TPicture เหมาะเมื่อ bitmap มาจากงาน
// render หรือการสร้างแบบแบตช์อยู่แล้ว
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;

โปรเจ็กต์ Demo\Delphi\ImageToPDF Demo\CBuilder\ImageToPDF และ Demo\Lazarus\ImageToPDF เปลี่ยนสิ่งนี้ให้เป็นเวิร์กโฟลว์แบตช์เต็มรูปแบบ โดยเลือกไฟล์รูปภาพหลายไฟล์ พรีวิวรายการที่เลือก สร้างหนึ่งหน้า PDF ต่อหนึ่งรูปภาพ ปรับสเกลรูปภาพแต่ละภาพให้พอดีกับหน้า A4 แนวตั้งหรือแนวนอน บันทึกผลลัพธ์ และเปิด PDF ที่สร้างขึ้น

การไฮไลต์การค้นหาใน viewer

การไฮไลต์ทุกผลลัพธ์บนหน้าปัจจุบัน

var
  MatchCount: Integer;
begin
  // วาดมาสก์ clYellow ทับทุกผลลัพธ์ที่พบในหน้าปัจจุบันของมุมมอง
  // การเปลี่ยนไปหน้าอื่นจะล้างไฮไลต์โดยอัตโนมัติ
  PdfView1.HighlightColor := clYellow;
  MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
  Status.Caption := Format('%d match(es) on this page', [MatchCount]);

  // ลบ overlay ด้วยโปรแกรมก่อนที่หน้าจะเปลี่ยน
  PdfView1.ClearHighlight;
end;

เลย์เอาต์ viewer และสีหน้า

โหมด fit และพื้นหลังหน้า

// FitMode ช่วยให้เอกสารยาวยังคงอยู่ในกรอบระหว่าง Resize หรือการเปลี่ยนหน้า
// การตั้ง Zoom โดยตรงจะยกเลิก FitMode (กลับเป็น pfmNone)
PdfView1.FitMode := pfmFitPage;       // ทั้งหน้าพอดีกับ viewport
// PdfView1.FitMode := pfmFitWidth;   // ความกว้างหน้าตรงกับความกว้าง viewport
// PdfView1.FitMode := pfmActualSize; // 100%

// PageColor แยกสีหน้ PDF ที่ render ออกจาก Color ของโฮสต์คอนโทรล
// — การตั้งค่า viewer โหมดมืดทั่วไปจะคงพื้นที่เลื่อนไว้เป็นสีเข้ม
// ขณะที่หน้ PDF ยังเป็นสีขาวกระดาษ
PdfView1.Color     := clBlack;        // พื้นที่เลื่อน
PdfView1.PageColor := clWhite;        // พื้นหลังหน้ PDF

// เงาตกกระทบและเส้นขอบหน้าที่เลือกใช้ เพื่อยกหน้าขึ้นจากพื้นหลังมืด
PdfView1.PageShadowSize  := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;

สำรวจการโต้ตอบของ viewer ใน ViewerInteractionLab

โปรเจ็กต์ Demo\Delphi\ViewerInteractionLab Demo\CBuilder\ViewerInteractionLab และ Demo\Lazarus\ViewerInteractionLab รวบรวมพื้นผิวการโต้ตอบของ TPdfView รุ่นใหม่ไว้ในรูปแบบกะทัดรัดเดียว: เปิดหรือสร้าง PDF ตัวอย่าง สลับ DisplayMode ใช้ FitMode เปลี่ยน PageColor หมุนหน้า ไฮไลต์ผลการค้นหาบนหน้าปัจจุบันด้วย HighlightSearchText เปิดหรือปิดการเลือกข้อความของผู้ใช้ ตรวจ SelectedText เรียก SelectAll CopySelectionToClipboard และ ClearSelection และดูพิกัด DeviceToPage อัปเดตในแถบสถานะขณะเมาส์เลื่อนไปบนหน้า

การค้นหาและเลือกข้อความใน SearchAndSelect

โปรเจ็กต์ Demo\Delphi\SearchAndSelect Demo\CBuilder\SearchAndSelect และ Demo\Lazarus\SearchAndSelect เปลี่ยนเวิร์กโฟลว์ viewer สำหรับการค้นหาและเลือกให้กลายเป็นตัวอย่างแบบสแตนด์อโลน พวกมันเปิดหรือสร้าง PDF แสดงผลการค้นหาทุกหน้าพร้อมหมายเลขหน้า ดัชนีอักขระ และข้อความพรีวิว รองรับการนำทาง Previous / Next และการดับเบิลคลิกเพื่อกระโดดไปยังผลลัพธ์ ไฮไลต์ผลลัพธ์บนหน้าปัจจุบันด้วย HighlightSearchText เปิดตัวเลือก match-case และ whole-word สลับ AllowUserTextSelection พรีวิว SelectedText ขับ SelectAll CopySelectionToClipboard และ ClearSelection จากปุ่ม และให้ผู้ใช้สลับระหว่างโหมดแสดงผลหน้าเดี่ยวกับแบบ spread

การอ่านค่าการพิมพ์ที่ผู้เขียนกำหนดใน PrintPreferences

โปรเจ็กต์ Demo\Delphi\PrintPreferences Demo\CBuilder\PrintPreferences และ Demo\Lazarus\PrintPreferences อ่านค่าการพิมพ์ที่ผู้เขียนกำหนดก่อนพิมพ์ พวกมันแสดง PrintCopies PrintPageRanges PrintScaling และ PrintPaperHandling สามารถคัดลอกจำนวนสำเนาและช่วงหน้าที่ผู้เขียนให้ไว้ไปยังอินพุตการพิมพ์ และแสดงวิธีที่ SetPdfPrintPaperHandlingDevMode แมปเจตนาการพิมพ์สองหน้าของ PDF ไปยัง DEVMODE ของ Windows

การพิมพ์ PDF มาตรฐานใน PrintPDF

โปรเจ็กต์ Demo\Delphi\PrintPDF Demo\CBuilder\PrintPDF และ Demo\Lazarus\PrintPDF แสดงเวิร์กโฟลว์การพิมพ์มาตรฐาน: เปิด PDF พรีวิวหน้า เลือกช่วงหน้าและจำนวนสำเนาในกล่องโต้ตอบการพิมพ์ของแพลตฟอร์ม เคารพโหมด collate เรนเดอร์แต่ละหน้าผ่าน TPdf.RenderPage และแสดงความคืบหน้าการพิมพ์พร้อมการยกเลิก ตัวอย่าง Delphi ยังรับ PDF ที่ลากมาวางเพื่อโหลดได้รวดเร็ว

การแสดงหลายหน้าอย่างรวดเร็วใน MultiPageViewer

โปรเจ็กต์ Demo\Delphi\MultiPageViewer Demo\CBuilder\MultiPageViewer และ Demo\Lazarus\MultiPageViewer ทดสอบการแสดงผลแบบต่อเนื่อง แบบ spread และแบบ aware-cover พร้อมเวิร์กโฟลว์การนำทางหน้าและการซูมตามปกติ พวกมันยังมีประโยชน์สำหรับทดสอบความเร็วจากการเปิดจนถึงหน้าแรกและการตอบสนองของการเรนเดอร์บน PDF ขนาดใหญ่มาก ในการทดสอบไฟล์ใหญ่ เอกสารขนาด 2.x GB ที่อาจทำให้ Acrobat หรือ Foxit ใช้เวลา 20-30 วินาทีก่อนแสดงมุมมองแรก สามารถใช้งานได้เกือบทันทีผ่านเส้นทาง viewer ของ PDFiumPas

การตรวจสอบแบบเคียงข้างใน SplitView

โปรเจ็กต์ Demo\Delphi\SplitView Demo\CBuilder\SplitView และ Demo\Lazarus\SplitView แสดงการตรวจสอบ PDF แบบเคียงข้าง ตัวอย่างของ C++Builder และ Lazarus เน้นการเปรียบเทียบแบบสองหรือสามพาเนล การเลือกมุมมองที่ใช้งานอยู่ คำสั่งนำทางที่ซิงก์กันได้ ตัวตั้งซูมที่ใช้ร่วมกัน และการหมุนสำหรับมุมมองที่เลือกหรือทุกมุมมองที่โหลดไว้ ใช้ตัวอย่างเหล่านี้เพื่อเปรียบเทียบเอกสารขนาดใหญ่โดยยังคงให้มุมมองแรกที่ใช้งานได้และการเรนเดอร์หน้าถัด ๆ ไปตอบสนองได้เหมือนเดิม ไฟล์ PDF ขนาด 2.x GB ชุดเดียวกันที่อาจทำให้ Acrobat หรือ Foxit ใช้เวลา 20-30 วินาทีในการเปิดก็สามารถใช้งานได้เกือบทันทีในเส้นทาง viewer ของ PDFiumPas

การตรวจสอบพื้นที่เสี่ยงของ PDF ใน SecurityAudit

โปรเจ็กต์ Demo\Delphi\SecurityAudit Demo\CBuilder\SecurityAudit และ Demo\Lazarus\SecurityAudit เปิด PDF แบบอ่านอย่างเดียวและแสดงสถานะสิทธิ์ ไฟล์แนบที่ฝังไว้ การกระทำ JavaScript ของเอกสาร คำอธิบายประกอบลิงก์ URI และ Launch ลิงก์เว็บ สรุปลายเซ็น สถานะ XFA ความพร้อมของตัวช่วย V8 และ callback ของฟีเจอร์ที่ไม่รองรับ แถวสรุปและแถว finding เดียวกันสามารถบันทึกหรือคัดลอกเป็นรายงาน audit แบบ TXT หรือบันทึกเป็น JSON แบบมีโครงสร้างได้ พวกมันมีไว้เป็นแผงความเสี่ยงของแอปพลิเคชันโฮสต์ ไม่ใช่ตัวตรวจจับมัลแวร์

การจัดการลิงก์ใน viewer

การคลิกตามลิงก์อย่างปลอดภัยด้วยรายการอนุญาตรายกระทำ

// LinkOptions ควบคุมการจัดการอัตโนมัติของ action link PDF ทั้ง 4 แบบ
// ค่าเริ่มต้นเปิดใช้เฉพาะ goto และ URI; launch (เรียกโปรแกรม) และ
// embedded GotoR (กระโดดไปยังไฟล์อื่น) ยังปิดอยู่เพื่อไม่ให้การคลิกพลาด
// สั่งรันโค้ดโดยพลการได้
PdfView1.LinkOptions :=
  [loAutoGoto, loAutoOpenURI];  // ค่าเริ่มต้นที่ปลอดภัย

// OnAnnotationLinkClick จะทำงานสำหรับปลายทางภายในเอกสาร (การกระโดดหน้า,
// ปลายทางที่ตั้งชื่อไว้, action chain) ตั้ง Handled = True เพื่อป้องกัน
// ไม่ให้ view ตามลิงก์อัตโนมัติ
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;  // ระงับการจัดการอัตโนมัติ
  end;
end;

// OnWebLinkClick จะทำงานสำหรับสตริง URI ที่ตัวสแกน web-link ของ PDFium
// ตรวจพบ (URL ที่ตรวจจับอัตโนมัติใน text stream แม้ไม่มีคำอธิบายประกอบ
// /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;

การแก้ไข widget ฟอร์มใน viewer

การเลือก และ Undo / Redo

// บน widget ของ AcroForm ที่โฟกัสอยู่ มีเมธอดใหม่ 6 ตัวสำหรับประวัติ
// การแก้ไขฟอร์มของ PDFium เมธอดทั้งหกจะหยุดทำงานอย่างปลอดภัยหากไม่มี
// widget ที่โฟกัสหรือเอกสารไม่มี 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;

การล็อก viewer สำหรับ kiosk

ปิดท่าทางนำทางหน้า

// ทั้งสามค่าจะเป็น True โดยค่าเริ่มต้นเพื่อให้แอปเดิมอัปเกรดได้โดยไม่
// เปลี่ยนพฤติกรรม สลับเป็น False สำหรับ kiosks, preview panes หรือ
// การฝังแบบอ่านอย่างเดียวโดยไม่ต้องสืบทอด TPdfView
PdfView1.AllowUserPageChange       := False;  // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // ล้อเลื่อนขึ้น/ลงจะข้ามหน้า
PdfView1.AllowUserTextSelection    := False;  // การเลือกข้อความด้วยเมาส์/แป้นพิมพ์

// Ctrl+wheel ถูกสงวนไว้สำหรับซูม และตัวจัดการการนำทางจะข้ามมันไม่ว่า
// ค่า ChangePageOnMouseScrolling จะเป็นอย่างไร

แผงภาพขนาดย่อด้านข้าง

การเชื่อม TPdfThumbnailView เข้ากับ TPdfView

// TPdfThumbnailView (unit FPdfThumbnail) แสดงภาพย่อของแต่ละหน้าเป็น
// หนึ่งแถวในแผงด้านข้างแบบเลื่อน
uses FPdfThumbnail;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ThumbView.Pdf              := Pdf1;
  ThumbView.ThumbnailWidth   := 120;
  ThumbView.ThumbnailHeight  := 160;
  ThumbView.SelectionColor   := $00CFA85F;  // ไฮไลต์โทนอุ่น
  ThumbView.OnPageClick      := ThumbViewPageClick;
end;

procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
  PdfView1.PageNumber := PageIndex + 1; // PageIndex เป็นแบบ zero-based
end;

procedure TForm1.PdfView1PageNumberเปลี่ยนแปลง(Sender: TObject);
begin
  ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;

การส่งออกแบตช์ที่ยกเลิกได้แบบร่วมมือ

แปลง PDF หลายไฟล์เป็น JPG พร้อมความคืบหน้าต่อไฟล์และต่อหน้า

// แบบที่ใช้โดย Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport,
// และ Demo\Lazarus\BatchExport หนึ่ง TPdf ต่อหนึ่งไฟล์,
// IPdfCancellationToken สำหรับการยกเลิกจาก UI, TPdfStreamAdapter สำหรับ
// การโหลดไฟล์แบบมีบัฟเฟอร์
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;

พิกัด viewer และการทดสอบการชน

จัดหน้าให้พอดีและตรวจข้อความใต้เมาส์

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;

การจัดการข้อผิดพลาด

การทำงานกับ 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 และการเข้าถึง

ตรวจ Tagged PDF และไล่ Structure Tree

// IsTagged เป็นการตรวจ catalog แบบเบา ๆ — จะเป็น True เมื่อมี /StructTreeRoot
// StructureElements ทำให้ tree กลายเป็นอาร์เรย์แบนของระเบียน
// TPdfStructureElement ที่มี type, title, alternate text, actual text,
// expansion text, language, level, parent index และจำนวน child / marked
// content / attribute
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;

การเลือกโหมดพิมพ์

การตั้งค่าโหมดพิมพ์ EMF / PostScript / Image-Mask

// SetPdfPrintMode เป็นฟังก์ชัน global ที่แมปกับ FPDF_SetPrintMode ของ PDFium
// ใช้ค่า TPdfPrintMode ที่ตรงกับไดรเวอร์เครื่องพิมพ์ที่ใช้งาน
SetPdfPrintMode(pmEmf);                   // GDI EMF (ค่าเริ่มต้น)
// SetPdfPrintMode(pmTextOnly);           // การเรนเดอร์แบบข้อความล้วน
// SetPdfPrintMode(pmPostScript2);        // PostScript ระดับ 2
// SetPdfPrintMode(pmPostScript3);        // PostScript ระดับ 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks);      // EMF + image masks
// SetPdfPrintMode(pmPostScript3Type42);  // PostScript 3 + ฟอนต์ Type 42

การอ่านค่าการพิมพ์ที่ผู้เขียนตั้งใจ

// PrintCopies, PrintPageRanges, PrintScaling และ PrintPaperHandling เป็น
// รายการใน viewer-preference dictionary — มันเข้ารหัสสิ่งที่ผู้เขียน
// เอกสารต้องการให้ PDF reader ใช้เป็นค่าเริ่มต้น
var
  Ranges: TPrintPageRanges;
  Range: TPrintPageRange;
begin
  ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
  ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
    Ord(Pdf1.PrintScaling)));

  Ranges := Pdf1.PrintPageRanges; // ช่วงแบบเริ่มที่ 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;

การกระทำ JavaScript ของเอกสาร

การ enumerate JavaScript ใน Name Tree

// JavaScript ระดับเอกสารถูกเก็บไว้ใน name tree /Names /JavaScript
// JavaScriptAction[i] คืนหนึ่งรายการ; JavaScriptActions คืนอาร์เรย์ทั้งหมด
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;

เคล็ดลับด้านประสิทธิภาพ

การปรับแต่งการทำงาน 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;

// เส้นทาง render แบบไม่คัดลอก (ค่าเริ่มต้นตั้งแต่ v1.21.0): RenderPage
// เขียนตรงลงไปยัง buffer DIB ของ TBitmap ปลายทางเมื่อผู้เรียกส่ง bitmap
// ที่จองไว้ล่วงหน้า ทำให้ไม่ต้องคัดลอกหนึ่งชุดขนาด width * height * 4 bytes
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;