Create a PDF viewer in a couple of hours

  • Tutorial
We haven’t written about Qt for a long time, so we’ll do something simple but powerful. The framework was created more than ten years ago (20 soon), but it still continues to please and amaze us thanks to the efforts of the Qt community.

I want to show an example of application development with a little effort at the junction of technologies for creating desktop applications and web programming.

A few weeks ago I was looking for a way to convert specific PDF documents into images, taking into account the possibility of automation and scripting in the future. Of course there is an old-timer - the ImageMagic package with the convert utility, but unfortunately I came across the fact that this tool is not as good as I expected on these files - it doesn’t render many files correctly and that wasn’t happy at all - many illustrations were corrupted.

I began to look for other tools, and although there are a lot of various utilities, each one has its own peculiarities, so I still haven't chosen which one to use.

Instead, I got the idea, can Qt, as a fairly mature technology, help me? In Qt, it is very simple to create a PDF document using QPrinter, but what about the reverse functionality - making an image from a PDF page? But there is another well-developed technology - PDF.js.

Can these two technologies be combined? Of course! Qt has a QWebEngineView component. Demonstrate in code:

Quick based on QMainWindow:

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

The result will be a window with a web page inside. Now it's PDF.js. The project has a fairly large amount of code, but it is possible to build a compact (minified) version that can be easily integrated into the website. Assembly example:

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

The result will be a “compiled” version of pdf.js in the build / minified folder, which we copy into our project. Set the start URL to the local file minified / web / viewer.html

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

Build and run:
image
It works fine, the approach is correct, but it shows the default PDF file. How can I pass a file name to javascript? For this, Qt has another great QWebChannel module. The idea is that on the C ++ / Qt side, a QWebChannel object is created and it is set by the channel for the loaded web page. With this channel, we can register objects that will be available already inside JavaScript code. From JavaScript, the Q_PROPERTY properties will be visible:

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);

The above code allows you to access the communicator object from JavaScript. Now you need to make changes to viewer.html / viewer.js, add the standard qwebchannel.js to make communication work - quite simply:

For viewer.html just add the inclusion qwebchannel.js. For viewer.js, add QWebChannel initialization and get the file name from the channel that will be used instead of the default file:

Code:

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

Here's how it works: Before the page loads, the web channel is attached and the communicator object is registered. Then, when viewer.html is loaded for the first time, the QWebChannel JS class is defined. After defining DEFAULT_URL, a JS QWebChannel object is created and as soon as communication is established, the attached js function will be called which reads the URL from the communicator object. The new URL will be used instead of the sample file. In the same way, you can transfer the rendered page as an image from JavaScript to the C ++ / Qt part of the application.

Having finished the changes in PDF.js, we just rebuild the minified:

$ gulp minified

Copy the minified version to the project folder. Let's finish getting the list of files from the command line arguments, etc.
image
Done, a working desktop PDF viewer application in a couple of hours.

GitHub

Also popular now: