Вбудовування панелі читання PDF у бізнес-додаток Delphi не вимагає написання циклу рендерингу, движка прокрутки або моделі масштабування. losLab PDF Library містить TPDFlibViewer — VCL-компонент, який відкриває документ одним викликом і надає виділення тексту, панорамування, масштабування рамкою, знімки екрана, підсвічування областей, підсвічування повнотекстового пошуку, взаємодію з анотаціями та друк як вбудовану поведінку. Ця стаття описує п'ять кроків, які перетворять порожню форму на робочий переглядач: розміщення компонента, підключення готових дій, додавання підсвічування пошуку, обробка кліків по анотаціях та друк
Чому варто використовувати компонент переглядача замість написання циклу рендерингу?
Аргумент розділення рівнів є першочерговим, оскільки він пояснює, що саме ви отримуєте. Рендеринг сторінки у бітову карту — це лише найпростіші 20 відсотків; інші 80 відсотків — це все, що її оточує: кеш бітових карт сторінок з лімітом витіснення, розмітка прокрутки для сторінок різних розмірів, режими масштабування, що перераховуються при зміні розміру, перевірка потрапляння (hit-testing), яка зіставляє піксель миші з координатами сторінки при будь-якому масштабі та повороті, а також планування перемальовування без мерехтіння. TPDFlibViewer бере все це на себе та делегує растрову роботу тому ж движку, який описано в статті про рендеринг з кількома движками, тому рівень управління та рівень рендерингу залишаються взаємозамінними окремо. Код вашої форми взаємодіє з Windows-компонентом прокрутки, а не з рендерером
Як вбудувати переглядач PDF у додаток Delphi?
TPDFlibViewer.LoadFromFile відкриває документ через власний внутрішній екземпляр TPDFlib компонента та повертає 1 у разі успіху, тому мінімальний переглядач — це виклик конструктора, призначення Parent та одне завантаження. Якщо ви вже маєте екземпляр TPDFlib в іншому місці додатка, AttachLibrary натомість відобразить його вибраний документ, причому володіння залишиться на вашому боці
uses PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
FViewer.ViewerMode := vmHand; // pan by dragging; a plain click still follows links
end;
Властивість ViewerMode вибирає, що робить миша, з п'яти значень: vmSelect — типовий інструмент виділення тексту, vmHand виконує панорамування перетягуванням із курсором-рукою, причому звичайний клік все одно переходить за посиланнями, vmZoom — це масштабування рамкою, яке збільшує виділену область на весь екран, vmSnapshot копіює виділену область у буфер обміну (з подією OnSnapshot та перемикачем SnapshotToClipboard), а vmHighlight малює постійні напівпрозорі підсвічування областей. Інструмент підсвічування заслуговує на окрему увагу щодо координат: області зберігаються в просторі користувача сторінки, а не в клієнтських пікселях, тому позначки AddHighlightRegion зберігаються при масштабуванні, повороті вигляду та навігації по сторінках без будь-яких додаткових обчислень з вашого боку. У повернутому вигляді напрямок перетягування може навіть йти проти осей сторінки, що дає негативну ширину або висоту після зворотного перетворення повороту — компонент нормалізує прямокутник за вас
Як підключити панель інструментів PDF у Delphi без допоміжного коду?
Модуль PDFlibViewerActions повністю усуває потребу в написанні допоміжного коду для панелі інструментів. Він надає двадцять готових нащадків TAction, які охоплюють навігацію по сторінках (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), масштабування (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), поворот вигляду (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), друк, діалоги відкриття та збереження файлів, копіювання виділеного тексту та по одній дії-перемикачу для кожного інструмента миші (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Кожна дія вказує на компонент через свою опубліковану властивість Viewer і підтримує власний стан: кожна дія вимикається, коли немає відкритого документа, дії навігації відстежують позицію сторінки, копіювання відстежує наявність виділення, а дії вибору режиму та вписування сторінки синхронізують свій стан прапорця з активним інструментом та режимом масштабування. У середовищі розробки всі вони реєструються в категорії «losLab PDF» редактора списку дій (Action List); у коді програми ті самі класи створюються всього трьома рядками кожен
uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;
procedure TMainForm.BuildToolbar;
var
NextPage: TPDFlibViewerNextPage;
FitWidth: TPDFlibViewerFitWidth;
HandMode: TPDFlibViewerHandMode;
begin
NextPage := TPDFlibViewerNextPage.Create(Self);
NextPage.ActionList := ActionList1;
NextPage.Viewer := FViewer;
btnNext.Action := NextPage;
FitWidth := TPDFlibViewerFitWidth.Create(Self);
FitWidth.ActionList := ActionList1;
FitWidth.Viewer := FViewer;
btnFitWidth.Action := FitWidth;
HandMode := TPDFlibViewerHandMode.Create(Self);
HandMode.ActionList := ActionList1;
HandMode.Viewer := FViewer;
btnHand.Action := HandMode;
end;
При створенні власної реєстрації дій на основі цього патерну варто знати про одну пастку з областю видимості модулів. RegisterActions оголошено в System.Actions, а не у Vcl.ActnList, і помилка компілятора, яку ви отримаєте, якщо вказати лише Vcl.ActnList у розділі uses, є оманливою: літерал масиву класів [TAction1, TAction2, ...] інтерпретується як множина, що призводить до ланцюжка помилок E2010 Incompatible types: 'Integer' and 'class of ...', які виглядають як невідповідність типів у ваших класах дій. Вирішення полягає в одному додатковому модулі в uses, але діагностика вказує куди завгодно, тільки не на це. Для панелей масштабування TPDFlibViewer.EffectiveZoomPercent повертає фактичний масштаб — включаючи обчислений коефіцієнт у режимах вписування по ширині та по сторінці — тому крок збільшення може базуватися на реальному поточному масштабі, а не на останньому значенні, яке ви призначили
Підсвічування результатів пошуку та зміна орієнтації координат
HighlightSearchHits шукає запит по всьому документу та малює напівпрозору смугу поверх кожного збігу, повертаючи кількість знайдених результатів; ClearSearchHighlights, SearchHighlightCount та властивість SearchHighlightColor доповнюють цей функціонал. Оскільки прямокутники збігів зберігаються в просторі сторінки та перепроектуються при кожному малюванні, смуги залишаються прив'язаними до тексту, коли користувач прокручує, масштабує або повертає вигляд. Якщо ви використовуєте допоміжну панель TPDFlibSearchPanel, її метод RunSearch автоматично малює такі ж смуги у підключеному переглядачі за допомогою властивості HighlightInView, яка увімкнена за замовчуванням
procedure TMainForm.btnSearchClick(Sender: TObject);
var
Hits: Integer;
begin
FViewer.SearchHighlightColor := clYellow;
Hits := FViewer.HighlightSearchHits(edtQuery.Text, False); // case-insensitive
StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;
Складність, і причина віддати перевагу вбудованому рішенню замість створення власного на основі необроблених результатів пошуку, полягає в перетворенні системи координат. Текстовий движок повідомляє прямокутники збігів у координатах, які залежать від налаштування Origin бібліотеки: при Origin 0 або 3 вісь Y спрямована знизу вгору, як у просторі користувача PDF, а при Origin 2 або 3 вісь X відлічується від правого краю. Шар накладання переглядача працює в просторі сторінки зверху вниз і зліва направо, тому кожен прямокутник збігу має бути перевернутий (Y = PageHeight - Bottom, і, де це застосовно, X = PageWidth - Right) перед збереженням — використання повідомленого верхнього краю «як є» за стандартної точки відліку призведе до малювання підсвічування у вертикально дзеркальному положенні на сторінці. TPDFlibViewer виконує це перетворення відповідно до активної точки відліку при записі кожного збігу, а потім застосовує пряме відображення повороту під час малювання, тому смуги лягають правильно під будь-яким кутом. Сама напівпрозорість використовує Windows API AlphaBlend із постійною прозорістю джерела, оскільки полотно VCL не має вбудованого альфа-змішування. Базові API для пошуку тексту та перерахування елементів сторінки детально описані в статті про пошук тексту та перерахування елементів сторінки
Кліки по анотаціях, підказки та експорт вкладень
TPDFlibViewer трактує анотації як інтерактивні об'єкти першого класу. Клік по анотації, яка не є посиланням, викликає подію OnAnnotClick із номером сторінки, 1-індексованим індексом анотації та рядком підтипу; AnnotAtPagePoint дозволяє виконати такий самий тест потрапляння програмно та пропускає спливаючі супутні анотації, тому ви завжди отримуєте саме ту анотацію, яку користувач бачить на екрані. Наведення курсору на анотацію відображає її текст Contents як підказку компонента за допомогою перемикача AnnotHints, який увімкнено за замовчуванням і який взаємодіє зі стандартною властивістю VCL ShowHint. Для анотацій типу FileAttachment метод SaveAnnotAttachmentToFile записує вбудований файл на диск і повертає 1 у разі успіху, що дозволяє реалізувати збереження за кліком всього в кілька рядків
procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
const Subtype: WideString);
begin
if Subtype = 'FileAttachment' then
if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
'C:\Temp\attached-invoice.xml') = 1 then
StatusBar1.SimpleText := 'Attachment saved';
end;
Друк та обмеження, які слід враховувати
Друк не вимагає окремої інтеграції. PrintDoc відображає стандартний діалог друку Windows — вибір принтера, діапазон сторінок, копії, розбирання по копіях — і передає завдання движку друку бібліотеки, тоді як PrintDoc(False) відправляє весь документ на стандартний принтер без будь-якого інтерфейсу користувача. PrintDocRange працює повністю програмно з явно вказаним ім'ям принтера, початковою та кінцевою сторінками, кількістю копій та прапорцем розбирання, що є правильним способом для пакетного або серверного друку з того самого екземпляра переглядача
Два обмеження заслуговують на чесну згадку. По-перше, TPDFlibViewer є Windows VCL-компонентом; рівень переглядача виключений зі збірок NOVCL, тому для консольних служб і проектів без VCL слід використовувати безпосередньо базовий API рендерингу TPDFlib. По-друге, використання без інтерфейсу користувача (headless) підтримується, але обмежене: CaptureRegion рендерить будь-яку клієнтську область — сторінки разом із підсвічуваннями — у бітову карту, якою володіє викликач, і працює на компоненті без батьківського елемента та дескриптора вікна, оскільки шлях малювання уникає викликів VCL, які б змусили створити дескриптор. Це робить практичною перевірку на рівні пікселів в автоматизованих тестах, але все, що дійсно потребує циклу повідомлень (наприклад, інтерактивні інструменти миші), все одно має виконуватися в рамках реального сеансу інтерфейсу. З точки зору продуктивності, компонент підтримує кеш сторінок у пам'яті з настроюваним лімітом через SetCacheLimit і може зберігати відрендерені сторінки між сеансами; стаття про дисковий кеш сторінок переглядача детально описує цей рівень та його поведінку при роботі з DPI для кожного монітора окремо
Отже, п'ять кроків: розмістити компонент і викликати LoadFromFile, підключити готові дії з PDFlibViewerActions, викликати HighlightSearchHits для пошуку на сторінці, обробити OnAnnotClick для роботи з анотаціями та викликати PrintDoc. Кожна з описаних тут частин постачається у стандартній бібліотеці losLab PDF Library для Delphi, де компонент переглядача, класи дій та движок рендерингу встановлюються разом