Средство просмотра PDF в Delphi сводится к двум компонентам и связи между ними. TPdf владеет документом: он открывает файл, расшифровывает его и отвечает на вопросы о количестве страниц и метаданных. TPdfView — это визуальный элемент управления, который отрисовывает страницы на экране и управляет прокруткой, масштабированием и страницей, которую в данный момент просматривает пользователь. Компонент PDFium оборачивает тот же механизм рендеринга, который поставляется в Chrome, поэтому глифы, сглаживание и цвет, которые вы получаете на холсте, соответствуют тому, что ваши пользователи уже видят в своем браузере. Работа заключается не в рендеринге. Она заключается в подключении объекта документа к представлению, загрузке без сбоев поврежденного или защищенного паролем файла и предоставлении пользователю небольшого набора элементов управления, которые придают средству просмотра завершенный вид: перелистывание страницы, изменение масштаба, подгонка страницы под окно
В этой статье описывается сборка в том порядке, в котором вы ее фактически создаете. Все здесь отрисовывает по одной странице за раз, что требуется большинству рабочих процессов обработки документов. Если вам нужны страницы, сложенные в один непрерывно прокручиваемый столбец, это уже другое решение макета, а не то, что здесь описывается
Связывание TPdf с TPdfView
Поместите TPdf и TPdfView на форму, затем укажите представлению, какой документ отображать. Это единственное присваивание и есть вся связь между невизуальным документом и элементом управления, который его отрисовывает
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf and PdfView were dropped at design time.
PdfView.Pdf := Pdf; // the view paints whatever this document holds
PdfView.FitMode := pfmFitWidth; // start the user at a sensible zoom
end;
Прежде чем что-либо из этого будет запущено, на компьютере должна быть установлена нативная библиотека PDFium. Компонент PDFium вызывает pdfium32.dll или pdfium64.dll в зависимости от целевой платформы, и документ просто отказывается открываться, если DLL не найдена. Поставляйте соответствующую DLL вместе с вашим исполняемым файлом или поместите ее туда, где ее найдет системный загрузчик. Сборки с поддержкой V8 существуют только для файлов PDF, содержащих JavaScript, который вы хотите выполнить, чего не делает обычное средство просмотра, поэтому используйте стандартную DLL, если у вас нет конкретных причин для обратного
Загрузка документа без доверия к вводу
Инстинкт подсказывает обернуть загрузку в try/except и рассматривать выброшенное исключение как ошибку. В данном случае этот инстинкт ошибочен, и если вы ошибетесь, получится средство просмотра, которое будет выглядеть нормально, пока кто-нибудь не передаст ему поврежденный файл. Установка Active := True не вызывает исключения при сбое загрузки. Компонент PDFium перехватывает внутреннюю ошибку и оставляет значение Active равным False, поэтому единственный честный способ узнать, открылся ли документ, — это снова прочитать свойство после его установки
procedure TFormMain.OpenDocument(const FileName: string);
begin
Pdf.FileName := FileName;
Pdf.Active := True; // never raises; failure leaves Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // the view tracks its own current page
UpdatePageLabel;
end;
Заслуживают внимания две вещи. Во-первых, PageNumber существует в обоих объектах, и они независимы. Pdf.PageNumber — это представление документа о текущей странице; PdfView.PageNumber — это страница, которую фактически отображает элемент управления, и это та страница, которую вы устанавливаете для перемещения пользователя по файлу. Установка одного свойства не изменяет другое, поэтому средство просмотра всегда управляет свойством представления. Во-вторых, индексация начинается с 1: страницы нумеруются от 1 до Pdf.PageCount, а не от 0, что может застать врасплох тех, кто привык к массивам с нулевым индексом
Обработка зашифрованного файла
Зашифрованные документы загружаются по тому же пути. Если пароль для открытия установлен до активации, документ расшифровывается при открытии; если он неверен или отсутствует, Active остается False, точно так же, как и для поврежденного файла. Поэтому восстановление заключается в запросе пароля и повторной попытке активации
procedure TFormMain.OpenWithPassword(const FileName: string);
var
Password: string;
begin
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
begin
if InputQuery('Password required', 'Password:', Password) then
begin
Pdf.Password := Password; // must be set before Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
end;
Поскольку ошибка происходит без уведомления как для неверного пароля, так и для поврежденного файла, вы не можете отличить одно от другого только по свойству Active. На практике это приемлемо для средства просмотра: пользователь либо вводит правильный пароль, либо узнает, что файл не откроется, и сообщение читается одинаково в любом случае
Пролистывание документа
При открытом документе навигация — это арифметика с PdfView.PageNumber, ограниченная Pdf.PageCount. Единственная реальная работа — это ограничение, чтобы кнопки никогда не выводили страницу за пределы допустимого диапазона, а кнопки первой и последней страницы оставались неактивными в начале и в конце файла
procedure TFormMain.GoToPage(NewPage: Integer);
begin
if not Pdf.Active then
Exit;
if NewPage < 1 then
NewPage := 1
else if NewPage > Pdf.PageCount then
NewPage := Pdf.PageCount;
PdfView.PageNumber := NewPage;
UpdatePageLabel;
end;
// the four navigation buttons reduce to one call each
procedure TFormMain.FirstClick(Sender: TObject); begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject); begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject); begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject); begin GoToPage(Pdf.PageCount); end;
Текстовое поле «перейти на страницу N» — это тот же вызов GoToPage, получающий проанализированное целое число, и ограничение покрывает случай, когда пользователь вводит 9999 в десятистраничном файле. Оставьте UpdatePageLabel как единственное место, где пишется «Страница 3 из 12», чтобы показания никогда не рассинхронизировались с тем, что отображается в представлении
Масштабирование: явные проценты и режимы подгонки
Масштабирование в TPdfView представлено в двух взаимодействующих вариантах, и понимание этого взаимодействия — это разница между элементом управления масштабированием, который работает корректно, и тем, который борется с пользователем. Прямой путь — это свойство Zoom, процентное значение, где 100 означает фактический размер. Другой путь — это FitMode, который указывает представлению вычислить масштаб за вас и продолжать пересчитывать его при изменении размера окна
// fixed magnifications
PdfView.Zoom := 100; // actual size
PdfView.Zoom := 50; // half
PdfView.Zoom := 200; // double
// let the view size the page to the window, and keep it sized on resize
PdfView.FitMode := pfmFitWidth; // page width fills the control
PdfView.FitMode := pfmFitPage; // whole page visible
PdfView.FitMode := pfmActualSize; // 1:1 with the document's points
Вот часть, на которой спотыкаются. Прямое назначение Zoom сбрасывает FitMode на pfmNone. Это правильное поведение, а не ошибка: в тот момент, когда пользователь выбирает точные 150%, представление больше не может соблюдать правило «подгонка по ширине», поскольку эти два запроса конфликтуют. Следствием для вашего пользовательского интерфейса является то, что кнопка увеличения и кнопка подгонки страницы по размеру являются взаимоисключающими состояниями, и панель инструментов должна делать активный режим видимым. Когда пользователь нажимает кнопку подгонки страницы по размеру, устанавливайте FitMode; когда он нажимает на числовой масштаб, устанавливайте Zoom и позвольте ему самостоятельно сбросить режим подгонки
Если вы предпочитаете самостоятельно вычислять значение подгонки, например, для заполнения ползунка масштаба текущим процентом подгонки, вспомогательные функции для каждой страницы предоставят вам числа без изменения режима. PageWidthZoom[N], PageZoom[N] и ActualSizeZoom[N] возвращают процент, который подогнал бы страницу N по ширине, подогнал бы ее целиком или отобразил бы в фактическом размере
// seed a zoom readout from the fit-to-width value of the current page
var
FitPercent: Double;
begin
FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;
Что на самом деле нужно готовому средству просмотра
Приведенное выше средство просмотра состоит из нескольких десятков строк, и оно уже выполняет ту работу, которая требуется рабочему процессу обработки документов: открывает файл, выдерживает поврежденный, показывает страницу, перемещается между страницами и изменяет масштаб вручную или путем подгонки. PDFium выполняет сложную работу незаметно. Встроенные шрифты разрешаются, аннотации и поля форм рисуются там, где их размещает документ, и страница, которую вы видите, совпадает с той, которую увидел бы пользователь Chrome, потому что их отрисовывает один и тот же движок
Исходя из этой базы, дополнения носят скорее инкрементальный, чем структурный характер. Выделение текста и поиск читают из того же текстового слоя, который PDFium уже строит; метаданные, такие как Pdf.Title и Pdf.Author, доступны для чтения в виде одного свойства; вращение и оттенки серого — это параметры рендеринга, которые вы передаете при отрисовке страницы в растровое изображение. Ничто из этого не меняет ту основу, которая у вас здесь есть, то есть объект документа, представление и связывающий их поток загрузки-затем-навигации. Настройте эту основу правильно, и все остальное станет просто оформлением
Компоненты TPdf и TPdfView, используемые на протяжении всей статьи, являются частью компонента PDFium для Delphi и C++Builder, который содержит полный справочник по средству просмотра на странице своего продукта