PDF четецът в Delphi се свежда до два компонента и връзката между тях. TPdf притежава документа: той отваря файла, декриптира го и отговаря на въпроси относно броя на страниците и метаданните. TPdfView е визуалната контрола, която рисува страниците на екрана и се справя с превъртането, мащабирането и страницата, която потребителят в момента разглежда. PDFium Component обвива същия енджин за рендиране, който се доставя вътре в Chrome, така че глифовете, заглаждането (anti-aliasing) и цветът, които получавате върху платното, съвпадат с това, което вашите потребители вече виждат в своя браузър. Работата не е в рендирането. Тя е в свързването на обекта на документа към изгледа, зареждането без срив при повреден или защитен с парола файл, и даването на потребителя на шепата контроли, които карат програмата за преглед да се чувства завършена: прелистване на страницата, промяна на мащаба, побиране на страницата в прозореца
Това преминава през тази сглобка в реда, в който всъщност я изграждате. Всичко тук рендира по една страница наведнъж, което е това, което повечето работни процеси с документи искат. Ако имате нужда от страници, подредени в една непрекъснато превъртаща се колона, това е различно решение за оформление и не е пътят тук
Свързване на 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 Component извиква pdfium32.dll или pdfium64.dll в зависимост от вашата целева платформа, и документът просто отказва да се отвори, ако DLL-ът не може да бъде намерен. Доставете съответстващия DLL до вашия изпълним файл или го поставете там, където системният товарач (system loader) ще го намери. Компилациите с активиран 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; // 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. Единствената реална работа е ограничаването (clamping), така че бутоните никога да не изтласкват страницата извън обхвата, а първият и последният бутон да остават деактивирани в краищата на файла
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, захранено от анализирано цяло число, а ограничението (clamp) покрива случая, когато потребителят напише 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 са на едно четене на свойство разстояние; ротацията и степените на сивото са опции за рендиране, които подавате, когато рисувате страница в растерна графика (bitmap). Никое от тези не променя гръбнака, който имате тук, а именно обекта на документа, изгледа и потока „зареждане-след-това-навигация“, който ги свързва. Направете този гръбнак правилно и останалото е декорация
Използваните навсякъде компоненти TPdf и TPdfView са част от PDFium Component за Delphi и C++Builder, който съдържа пълната справка за програмата за преглед на продуктовата си страница