Переглядач PDF у Delphi зводиться до двох компонентів і проводки між ними. TPdf володіє документом: він відкриває файл, розшифровує його й відповідає на питання про кількість сторінок і метадані. TPdfView є візуальним контролом, який малює сторінки на екрані й опікується прокруткою, масштабом і тією сторінкою, на яку користувач наразі дивиться. PDFium Component обгортає той самий рушій рендерингу, що постачається всередині Chrome, тож гліфи, згладжування та колір, які ви отримуєте на канві, збігаються з тим, що ваші користувачі вже бачать у своєму браузері. Робота не в рендерингу. Вона в тому, щоб з'єднати об'єкт документа з переглядом, завантажити файл, не впавши на пошкодженому чи захищеному паролем, і дати користувачеві ту жменьку контролів, яка робить переглядач завершеним: перегорнути сторінку, змінити масштаб, вписати сторінку у вікно
Далі йде це складання в тому порядку, у якому ви його справді будуєте. Усе тут рендерить одну сторінку за раз, а це те, чого хоче більшість документних робочих процесів. Якщо вам потрібні сторінки, складені в одну безперервно прокручувану колонку, це інше рішення щодо компонування й не той шлях, що тут
Проводка TPdf до TPdfView
Киньте TPdf та TPdfView на форму, а потім скажіть перегляду, який документ показувати. Це єдине присвоєння і є всім зв'язком між невізуальним документом та контролом, який його малює
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf і PdfView кинуто на форму на етапі проєктування.
PdfView.Pdf := Pdf; // перегляд малює те, що містить цей документ
PdfView.FitMode := pfmFitWidth; // починаємо з розумного масштабу
end;
Перш ніж усе це запуститься, нативна бібліотека PDFium має бути на машині. PDFium Component звертається до pdfium32.dll чи pdfium64.dll залежно від вашої цільової платформи, і документ просто відмовляється відкриватися, якщо DLL не знайдено. Постачайте відповідну DLL поряд із виконуваним файлом або покладіть її туди, де її знайде системний завантажувач. Збірки з увімкненим V8 існують лише для тих PDF, що несуть JavaScript, який ви хочете виконати, а звичайному переглядачеві це не потрібно, тож беріть стандартну DLL, доки у вас не буде конкретної причини вчинити інакше
Завантаження документа без довіри до входу
Інстинкт підказує обгорнути завантаження в try/except і трактувати кинутий виняток як невдачу. Цей інстинкт тут хибний, і помилка на цьому дає переглядач, який виглядає добре, доки хтось не подасть йому зламаний файл. Присвоєння Active := True не піднімає винятку при невдалому завантаженні. PDFium Component ловить внутрішню помилку й лишає 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; // перегляд веде власну поточну сторінку
UpdatePageLabel;
end;
Дві речі заслуговують на увагу. Перша полягає в тому, що 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;
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 Component для Delphi та C++Builder, чия сторінка продукту містить повний довідник переглядача