Средство просмотра PDF в Delphi сводится к двум компонентам и связи между ними. TPdf владеет документом: он открывает файл, расшифровывает его и отвечает на вопросы о количестве страниц и метаданных. TPdfView — это визуальный элемент управления, который отрисовывает страницы на экране и управляет прокруткой, масштабированием и страницей, которую в данный момент просматривает пользователь. Компонент PDFium оборачивает тот же механизм рендеринга, который поставляется в Chrome, поэтому глифы, сглаживание и цвет, которые вы получаете на холсте, соответствуют тому, что ваши пользователи уже видят в своем браузере. Работа заключается не в рендеринге. Она заключается в подключении объекта документа к представлению, загрузке без сбоев поврежденного или защищенного паролем файла и предоставлении пользователю небольшого набора элементов управления, которые придают средству просмотра завершенный вид: перелистывание страницы, изменение масштаба, подгонка страницы под окно
В этой статье описывается сборка в том порядке, в котором вы ее фактически создаете. Все здесь отрисовывает по одной странице за раз, что требуется большинству рабочих процессов обработки документов. Если вам нужны страницы, сложенные в один непрерывно прокручиваемый столбец, это уже другое решение макета, а не то, что здесь описывается
Связывание TPdf с TPdfView
Поместите TPdf и TPdfView на форму, затем укажите представлению, какой документ отображать. Это единственное присваивание и есть вся связь между невизуальным документом и элементом управления, который его отрисовывает
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf и PdfView добавлены на форму в дизайнере.
PdfView.Pdf := Pdf; // представление рисует всё, что содержит документ
PdfView.FitMode := pfmFitWidth; // начните работу пользователя с разумного масштаба
end;
Прежде чем что-либо из этого будет запущено, на компьютере должна быть нативная библиотека PDFium. PDFium Component загружает pdfium.dll и на Win32, и на Win64: обе сборки носят одно имя файла и поставляются в отдельных папках DLLs\Win32 и DLLs\Win64, а документ просто откажется открываться, если 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; // никогда не возбуждает исключение; при сбое Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // представление ведёт свою текущую страницу
PdfView.Active := True; // пока представление не активно, оно не держит ни одной страницы
UpdatePageLabel;
end;
У представления есть собственный переключатель Active, и пока он не равен True, элемент управления не держит ни одной страницы: одно лишь открытие документа и установка PageNumber оставляют просмотрщик пустым. Ещё две вещи заслуживают внимания. Первая: PageNumber существует у обоих объектов, и это независимые величины. Pdf.PageNumber — представление документа о текущей странице; PdfView.PageNumber — страница, которую элемент управления реально показывает, и именно её вы выставляете, чтобы вести пользователя по файлу. Установка одной не двигает другую, поэтому просмотрщик всегда управляет свойством представления. Вторая — индексация с единицы: страницы идут от 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; // нужно выставить до Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
PdfView.Active := True;
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;
// четыре кнопки навигации сводятся к одному вызову каждая
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, который указывает представлению вычислить масштаб за вас и продолжать пересчитывать его при изменении размера окна
// фиксированные масштабы
PdfView.Zoom := 100; // фактический размер
PdfView.Zoom := 50; // половина
PdfView.Zoom := 200; // двойной
// пусть представление подгоняет страницу под окно и сохраняет размер при изменении окна
PdfView.FitMode := pfmFitWidth; // ширина страницы заполняет элемент управления
PdfView.FitMode := pfmFitPage; // видна вся страница
PdfView.FitMode := pfmActualSize; // 1:1 с пунктами документа
Вот часть, на которой спотыкаются. Прямое назначение Zoom сбрасывает FitMode на pfmNone. Это правильное поведение, а не ошибка: в тот момент, когда пользователь выбирает точные 150%, представление больше не может соблюдать правило «подгонка по ширине», поскольку эти два запроса конфликтуют. Следствием для вашего пользовательского интерфейса является то, что кнопка увеличения и кнопка подгонки страницы по размеру являются взаимоисключающими состояниями, и панель инструментов должна делать активный режим видимым. Когда пользователь нажимает кнопку подгонки страницы по размеру, устанавливайте FitMode; когда он нажимает на числовой масштаб, устанавливайте Zoom и позвольте ему самостоятельно сбросить режим подгонки
Если вы предпочитаете самостоятельно вычислять значение подгонки, например, для заполнения ползунка масштаба текущим процентом подгонки, вспомогательные функции для каждой страницы предоставят вам числа без изменения режима. PageWidthZoom[N], PageZoom[N] и ActualSizeZoom[N] возвращают процент, который подогнал бы страницу N по ширине, подогнал бы ее целиком или отобразил бы в фактическом размере
// возьмите показания масштаба из значения «по ширине» текущей страницы
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, который содержит полный справочник по средству просмотра на странице своего продукта