Создаём PDF просмотрщик за пару часов

в 14:55, , рубрики: qt, Qt pdf.js, метки:

Давно про Qt не писали, потому сделаем что-то простое но мощное. Фреймворк был создан уже более десяти лет тому (скоро и 20), но всё ещё продолжает нас радовать и удивлять благодаря усилиям Qt сообщества.

Я хочу показать пример разработки приложения с затратой небольших усилий на стыке технологий создания десктопных приложений и веб-программирования.

Несколько недель тому я искал способ конвертации специфических PDF документов в изображения с учетом возможности автоматизации и скриптования в будущем. Конечно есть старожил — пакет ImageMagic с утилитой convert, но к сожалению я столкнулся с тем что этот инструмент не так хорош как я ожидал именно на этих файлах — не рендерит корректно многие файлы и что совсем не радовало — многие иллюстрации были испорчены.

Я стал искать другие инструменты и хотя всевозможных утилит очень много но у каждой есть свои особенности так что я так и не выбрал какую использовать.

Вместо этого у меня появилась идея, может ли Qt как довольно зрелая технология помочь мне? В Qt очень просто создать PDF документ с помощью QPrinter, но как насчет обратной функциональности  - сделать изображение из PDF страницы? А ведь есть ещё одна хорошо проработаная технология — PDF.js.

Можно ли совместить эти две технологии? Конечно! Qt имеет компонент QWebEngineView. Продемонстрируем в коде:

По быстрому на основе QMainWindow:

m_webView = new QWebEngineView(this);
m_webView->load(url);
setCentralWidget(m_webView);

Результатом будет окно с веб страницей внутри. Теперь очередь PDF.js. Проект имеет довольно большой объём кода, но есть возможность собрать компактную (minified) версию которую можно легко встроить в вебсайт. Пример сборки:

$ git clone git://github.com/mozilla/pdf.js.git
$ cd pdf.js
$ brew install npm
$ npm install -g gulp-cli
$ npm install
$ gulp minified

Результатом будет «скомпилированая» версия pdf.js в папке build/minified, который копируем в наш проект. Установим стартовый URL на локальный файл minified/web/viewer.html

auto url = QUrl::fromLocalFile(app_path+"/minified/web/viewer.html");

Соберём и запустим:

image

Работает отлично, подход правильный, но показывает PDF файл по умолчанию. Как можно передать имя файла в среду javascript? Для этого у Qt есть другой отличный модуль QWebChannel. Идея в том, что на стороне C++/Qt создаётся QWebChannel объект и он устанавливается каналом(channel) для загружаемой веб страницы. С этим каналом мы можем зарегистрировать объекты которые будут доступны уже внутри JavaScript кода. Из JavaScript будут видны Q_PROPERTY свойства:

auto url = QUrl::fromLocalFile(app_path+"/minified/web/viewer.html");

m_communicator = new Communicator(this);
m_communicator->setUrl(pdf_path);

m_webView = new QWebEngineView(this);

QWebChannel * channel = new QWebChannel(this);
channel->registerObject(QStringLiteral("communicator"), m_communicator);
m_webView->page()->setWebChannel(channel);
m_webView->load(url);

setCentralWidget(m_webView);

Приведённый код позволяет получить доступ к объекту communicator из JavaScript. Теперь необходимо внести изменения в viewer.html/viewer.js, добавить стандартный qwebchannel.js чтобы заработала коммуникация — довольно просто:

Для viewer.html просто добавим включение qwebchannel.js
Для viewer.js добавим инициализацию QWebChannel и получим из канала имя файла который будет использоваться вместо файла по-умолчанию:

Код:

var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf'; 
new QWebChannel(qt.webChannelTransport 
       ,function(channel) {
           var comm = channel.objects.communicator;
           DEFAULT_URL = comm.url;
...

Вот как это работает: Перед загрузкой страницы, прикрепляется web channel и регистрируется communicator объект. Потом, когда viewer.html грузится в первый раз, определяется QWebChannel JS класс. После определения DEFAULT_URL создается JS QWebChannel объект и как только коммуникация установлена, будет вызвана прикреплённая js функция которая читает URL из объекта communicator. Новый URL и будет пользоваться вместо файла примера. Таким же образом можно передать отрендериную страницу как изображение из JavaScript в C++/Qt часть приложения.

Закончив изменения в PDF.js просто пересоберем minified:

$ gulp minified

Скопируем minified версию в папку проекта. Доделаем получение списка файлов из аргументов командной строки итд.

image

Готово, рабочее десктопное приложение PDF viewer за пару часов.

GitHub: https://github.com/yshurik/qpdfjs

Автор: yshurik

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js