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;
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;
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\FontProperties, Demo\CBuilder\FontProperties и Demo\Lazarus\FontProperties показывают те же метаданные через TPdf и TPdfView.
Проекты Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab и Demo\Lazarus\ContentExtractionLab превращают извлечение содержимого в единый рабочий процесс. Они открывают или создают PDF, позволяют пользователям выбрать метаданные, текст страниц, объекты страниц, изображения, вложения, ссылки, аннотации, закладки и метрики шрифтов / символов, затем генерируют сводку плюс подробный отчёт TXT или JSON. Лаборатория также включает выбор всех / ни одной категории, копирование отчёта в буфер обмена и действия сохранения исходного 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;
// Без сплющивания FormField[i] := value пишет только запись /V —
// видимый внешний вид берётся из потока /AP и может не обновиться.
// GenerateFormAppearances регенерирует /AP для каждого виджета, затем
// FlattenAllPages запекает аннотации и виджеты каждой страницы
// в постоянное, нередактируемое содержимое.
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');
// SaveAsPdfA постобрабатывает базовое сохранение инкрементальным
// обновлением, которое внедряет поток метаданных XMP, ICC OutputIntent
// sRGB и обновлённый 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;
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.Description);
end;
Проекты Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab и Demo\Lazarus\StandardsLab загружают или создают PDF, запускают ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR и ValidatePdfVT, затем показывают каждый обнаруженный уровень соответствия и количество проблем в одной сетке. Кнопки сохранения вызывают SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR и SaveAsPdfVT, благодаря чему рабочий процесс маркерного вывода виден без написания собственной обвязки.
Модуль FPdfPreflightReport объединяет ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR и ValidatePdfVT в один объект отчёта. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport и Demo\CBuilder\PreflightReport позволяют пользователям выбрать стандарты, сгенерировать сводку, изучить статус, приоритет, категории проблем, счётчики категорий, следующие действия, коды проблем и рекомендуемые действия, переключать предпросмотр между текстом, Markdown, JSON и CSV и экспортировать отчёты TXT, HTML, Markdown, JSON или CSV. Образец Delphi GUI также добавляет предпросмотр Action Plan, который сводит непройденные стандарты, приоритеты, коды проблем и рекомендации в список устранения. Demo\Delphi\PreflightReportCli предоставляет тот же путь отчёта как консольный рабочий процесс с аргументами входа/выхода, опциональным паролем, выбором формата вывода, включающим plan / actionplan, фильтрацией стандартов, опциональным встраиванием отчёта в один файл attach=output.pdf, кодами выхода для CI-гейтов failon=, обработкой batch=list.txt, сканированием каталога batchdir=folder с опциональной рекурсией и устойчивой обработкой с сортировкой путей, маршрутизацией отчётов outdir=reports, безопасными от коллизий именами отчётов для дублирующихся имён входных файлов, сводками TXT / JSON / HTML с агрегатными и стандартными итогами статусов, манифестами строк CSV с настройками запуска, повторяющимися для каждого элемента, и режимом образца без аргументов для скриптов и CI-задач. Сгенерированный отчёт указывает, что встроенные валидаторы покрывают проверки маркерного уровня и выбранные проверки уровня файла, поэтому полная препроверка уровня содержимого по-прежнему может быть делегирована специализированному движку валидации.
// LoadCustomDocument позволяет PDFium забирать блоки из любого
// позиционируемого TStream по требованию. Поток НЕ копируется в память,
// поэтому многогигабайтные PDF, удалённые потоки тел HTTP и БД-источники
// все жизнеспособны без предварительного буфера.
var
FileStream: TFileStream;
begin
// Потоковое чтение из файла (без копии в памяти)
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 для семантики «отмена из интерфейса»,
// которая работает в длительных путях экспорта и рендеринга.
uses FPdfAsync;
var
CancelSource: IPdfCancellationTokenSource;
CancelToken: IPdfCancellationToken;
begin
CancelSource := PdfCancellationTokenSource;
CancelToken := CancelSource.Token;
// ... пользователь нажимает «Отмена» ...
CancelSource.Cancel;
// Worker периодически проверяет CancelToken.IsCancelled, либо путь
// рендеринга опрашивает его через обратный вызов IFSDK_PAUSE PDFium.
if CancelToken.IsCancelled then
Memo1.Lines.Add('Cancelled by user');
end;
// RenderPageProgressive позволяет длительному рендерингу с высоким DPI
// прерваться в середине страницы, когда пользователь нажимает «Отмена»,
// вместо блокировки вызывающего до завершения.
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<T> запускает worker в фоновом потоке и отправляет конверт
// TPdfFutureResult<T> обратно в главный поток.
//
// Важно: TPdf сериализует вызовы рендеринга для каждого экземпляра, но
// worker всё ещё владеет диспетчеризацией и временем жизни документа.
// Не мутируйте TPdf во время выполняющегося рендеринга; пакетный экспорт
// обычно держит по одному 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, чтобы вызывающие могли
// декларативно построить преобразование и передать его в
// 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); // постумножение на другую матрицу
// Применить к любому объекту страницы PDFium (текст / путь / изображение / Form XObject)
RawMatrix := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawMatrix);
finally
M.Free;
end;
end;
// ImportPagesByIndex копирует явный массив нулевых индексов из исходного
// 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 (от единицы: страницы 1, 3, 5) из Source
// и добавить их в конец Pdf1.
Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);
// Пустой массив импортирует все страницы источника
// Pdf1.ImportPagesByIndex(Source, [], 0);
finally
Source.Free;
end;
end;
// ImportNPagesToOne возвращает совершенно новый TPdf, страницы которого
// являются композициями NumX*NumY ЭТОГО документа. OutputWidth/Height —
// это пользовательские единицы PDF (1 единица = 1/72 дюйма).
var
Composite: TPdf;
begin
Pdf1.FileName := 'C:\Slides.pdf';
Pdf1.Active := True;
// 4-up A4 альбомная: 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 принимает нулевые индексы перемещаемых страниц плюс индекс
// назначения для первой перемещённой страницы после завершения перемещения.
// Другие страницы сдвигаются вокруг перемещаемого блока, благодаря чему
// длина документа остаётся прежней.
//
// Пример: в 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');
// CreateXObjectFromPage создаёт переиспользуемый дескриптор Form XObject
// из любой страницы любого другого TPdf. Штампуйте этот дескриптор на
// столько страниц ЭТОГО документа, сколько нужно — каждый штамп
// представляет собой один объект страницы PDFium, который можно
// позиционировать, масштабировать и поворачивать через матричный 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; // закрывает дескриптор 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(FileName) принимает любой формат, зарегистрированный в
// графических модулях VCL или LCL (BMP, PNG через PngImage, JPG, ...).
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);
// AddImage(TBitmap) пропускает промежуточный TPicture, что идеально,
// когда растровое изображение уже получено из рендеринга или пакетной
// генерации.
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.
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]);
// Программно убрать наложение перед сменой страницы
PdfView1.ClearHighlight;
end;
// FitMode удерживает длинные документы в кадре при изменении размера
// или смене страницы. Прямая установка Zoom отменяет FitMode (он
// возвращается к pfmNone).
PdfView1.FitMode := pfmFitPage; // вся страница в области просмотра
// PdfView1.FitMode := pfmFitWidth; // ширина страницы совпадает с шириной области просмотра
// PdfView1.FitMode := pfmActualSize; // 100%
// PageColor отделяет отрисованную страницу PDF от Color управляющего хоста
// — типичная настройка просмотрщика тёмной темы оставляет область прокрутки
// тёмной, а сама страница PDF остаётся белой как бумага.
PdfView1.Color := clBlack; // область прокрутки
PdfView1.PageColor := clWhite; // фон страницы PDF
// Опциональная отбрасываемая тень и рамка страницы, чтобы приподнять
// страницу над тёмным фоном
PdfView1.PageShadowSize := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;
Проекты Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab и Demo\Lazarus\ViewerInteractionLab держат современную поверхность взаимодействия TPdfView в одной компактной форме: открыть или создать образец PDF, переключить DisplayMode, применить FitMode, изменить PageColor, повернуть страницы, подсветить совпадения поиска на текущей странице через HighlightSearchText, включить или выключить пользовательское выделение текста, изучить SelectedText, вызвать SelectAll, CopySelectionToClipboard и ClearSelection и наблюдать обновление координат DeviceToPage в строке состояния при движении мыши над страницей.
Проекты Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect и Demo\Lazarus\SearchAndSelect превращают рабочий процесс поиска и выделения в просмотрщике в самостоятельные образцы. Они открывают или создают PDF, перечисляют результаты поиска по всем страницам с номером страницы, индексом символа и предпросмотром текста, поддерживают навигацию «Предыдущее / Следующее» и переход к результату по двойному клику, подсвечивают совпадения текущей страницы через HighlightSearchText, предоставляют опции учёта регистра и целого слова, переключают AllowUserTextSelection, показывают предпросмотр SelectedText, управляют SelectAll, CopySelectionToClipboard и ClearSelection с кнопок и позволяют пользователям переключаться между одиночными и развёрнутыми режимами отображения.
Проекты Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences и Demo\Lazarus\PrintPreferences читают настройки печати автора перед печатью. Они отображают PrintCopies, PrintPageRanges, PrintScaling и PrintPaperHandling, могут копировать предоставленные автором копии и диапазоны во входные параметры печати и показывают, как SetPdfPrintPaperHandlingDevMode отображает намерение дуплекса PDF в Windows DEVMODE.
Проекты Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF и Demo\Lazarus\PrintPDF показывают стандартный процесс печати: открыть PDF, предпросмотреть страницы, выбрать диапазоны страниц и копии в платформенном диалоге печати, учитывать режим разбора, рендерить каждую страницу через TPdf.RenderPage и предоставлять прогресс печати с отменой. Образец Delphi также принимает PDF-файлы, перетащенные мышью, для быстрой загрузки.
Проекты Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer и Demo\Lazarus\MultiPageViewer используют непрерывный, развёрнутый и учитывающий обложку просмотр со штатными рабочими процессами навигации по страницам и масштабирования. Они также полезны для тестирования скорости открытия первой страницы и отзывчивости рендеринга на очень больших PDF: в тестах с большими файлами документы по 2+ ГБ, на которые Acrobat или Foxit могут тратить 20–30 секунд до первого просмотра, могут стать пригодными к использованию практически мгновенно через путь просмотрщика PDFiumPas.
Проекты Demo\Delphi\SplitView, Demo\CBuilder\SplitView и Demo\Lazarus\SplitView показывают параллельный обзор PDF. Образцы C++Builder и Lazarus посвящены сравнению двух или трёх панелей, выбору активного представления, опциональным синхронизированным командам навигации, общим пресетам масштабирования и повороту для выбранного представления или всех загруженных представлений. Используйте эти образцы для сравнения больших документов, сохраняя при этом отзывчивость первого пригодного просмотра и последующего рендеринга страниц; те же PDF-файлы по 2+ ГБ, на открытие которых Acrobat или Foxit могут тратить 20–30 секунд, могут стать пригодными к использованию практически мгновенно через путь просмотрщика PDFiumPas.
Проекты Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit и Demo\Lazarus\SecurityAudit открывают PDF только для чтения и перечисляют состояние разрешений, встроенные вложения, действия JavaScript документа, аннотации-ссылки URI и Launch, веб-ссылки, сводки подписей, состояние XFA, доступность помощников V8 и обратные вызовы неподдерживаемых функций. Те же строки сводки и находок можно сохранить или скопировать как аудиторский отчёт TXT или сохранить как структурированный JSON. Они предназначены как панели рисков приложения-хоста, а не как обнаружение вредоносных программ.
// LinkOptions управляет автоматической обработкой четырёх типов
// действий PDF-ссылок. По умолчанию включены только goto и URI; запуск
// программы (loAutoLaunch) и встроенный GotoR (переход к другому файлу)
// остаются ВЫКЛ, чтобы случайный клик не мог запустить произвольный код.
PdfView1.LinkOptions :=
[loAutoGoto, loAutoOpenURI]; // безопасные значения по умолчанию
// OnAnnotationLinkClick срабатывает для адресов назначения внутри
// документа (переходы по страницам, именованные адреса, цепочки действий).
// Установите Handled := True, чтобы запретить автоследование по ссылке.
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, обнаруженных сканером
// веб-ссылок PDFium (автообнаруженные URL в текстовом потоке, даже без
// аннотации /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;
// На сфокусированном виджете AcroForm шесть новых методов управляют
// историей редактирования формы PDFium. Все шесть безопасно завершают
// работу, если ни один виджет не сфокусирован или документ не несёт
// 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;
// Все три по умолчанию равны True, поэтому существующие приложения
// обновляются без изменения поведения. Установите их в False для
// киосков, панелей предпросмотра или встраиваний только для чтения
// без наследования TPdfView.
PdfView1.AllowUserPageChange := False; // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // переходы колесом вверх/вниз
PdfView1.AllowUserTextSelection := False; // выделение текста мышью/клавиатурой
// Ctrl+колесо зарезервировано для масштабирования и пропускается
// обработчиком навигации независимо от ChangePageOnMouseScrolling.
// TPdfThumbnailView (модуль 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 отсчитывается от нуля
end;
procedure TForm1.PdfView1PageNumberИзменено(Sender: TObject);
begin
ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;
// Шаблон, используемый Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport
// и Demo\Lazarus\BatchExport. Один TPdf на файл, IPdfCancellationToken
// для «отмены из интерфейса», 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;
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;
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;
// IsTagged — дешёвая проверка каталога — True, когда существует
// /StructTreeRoot. StructureElements материализует дерево в плоский
// массив записей TPdfStructureElement с типом, заголовком,
// альтернативным текстом, фактическим текстом, расширительным текстом,
// языком, уровнем, индексом родителя и счётчиками потомков /
// маркированного содержимого / атрибутов.
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;
// SetPdfPrintMode — глобальная функция, которая отображается в
// FPDF_SetPrintMode PDFium. Используйте подходящее значение TPdfPrintMode
// для используемого драйвера принтера.
SetPdfPrintMode(pmEmf); // GDI EMF (по умолчанию)
// SetPdfPrintMode(pmTextOnly); // рендеринг только текста
// SetPdfPrintMode(pmPostScript2); // PostScript Level 2
// SetPdfPrintMode(pmPostScript3); // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks); // EMF + маски изображений
// SetPdfPrintMode(pmPostScript3Type42); // PostScript 3 + шрифты Type 42
// PrintCopies, PrintPageRanges, PrintScaling и PrintPaperHandling —
// это записи словаря настроек просмотрщика. Они кодируют то, что автор
// документа хотел использовать как значения по умолчанию для читателей PDF.
var
Ranges: TPrintPageRanges;
Range: TPrintPageRange;
begin
ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
Ord(Pdf1.PrintScaling)));
Ranges := Pdf1.PrintPageRanges; // диапазоны от единицы
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 уровня документа хранится в дереве имён /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;
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;
// Путь рендеринга без копирования (по умолчанию с v1.21.0): RenderPage
// пишет напрямую в буфер DIB назначения TBitmap, когда вызывающие
// передают предварительно выделенное растровое изображение, избегая
// одной копии размером ширина * высота * 4 байта.
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;