Merge lp:~srazi/qpdfview/update-fitz-to-1-13 into lp:qpdfview

Proposed by Razi Alavizadeh
Status: Merged
Merged at revision: 2079
Proposed branch: lp:~srazi/qpdfview/update-fitz-to-1-13
Merge into: lp:qpdfview
Diff against target: 423 lines (+205/-52)
5 files modified
fitz-plugin.pro (+1/-1)
sources/fitzmodel.cpp (+127/-37)
sources/fitzmodel.h (+4/-0)
sources/pluginhandler.cpp (+67/-13)
sources/pluginhandler.h (+6/-1)
To merge this branch: bzr merge lp:~srazi/qpdfview/update-fitz-to-1-13
Reviewer Review Type Date Requested Status
Adam Reichold Pending
Review via email: mp+345782@code.launchpad.net

Commit message

Various update to Fitz plugin:

1- Update code based on current MuPDF API.
2- Add support to open: XPS, OpenXPS, CBZ, EPUB and FictionBook2
3- Add support to copy/search text

To post a comment you must log in.
Revision history for this message
Razi Alavizadeh (srazi) wrote :

Hello Adam,

Feel free to review it when you have enough free time. :)

Known issue:
1- Sometimes, FitzPage::text() returns wrong text.

2- I didn't implement textCache(), as I think the better solution is reuse textCache code in PDFModel.

3- Some external links are like: file://foo.pdf#page=bar that again should be fixed elsewhere.

4- I was able to compile it on Manjaro Linux, and plugin was loaded correctly. But the application crashed when creating main fz_context.

Best Regards,
Razi.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello Razi,

merged with minor changes, but nevertheless please test trunk again after the merge. Thanks again for your contribution!

Best regards, Adam.

P.S.: I had to remove -lmujs since it is not built OpenSUSE, but also replaced all the dependencies by -lmupdfthird we seems to be intended for that. Should be simple to modify via the qmake command line in any case.

Revision history for this message
Razi Alavizadeh (srazi) wrote :

Hello Adam,

It works correctly, thanks.

On Manjaro I have to add "-ljbig2dec -lopenjp2 -ljpeg" to LIBS. And it also works there without crash that I mentioned above as the 4th known issue.

There is also an issue when opening right to left EPUB files. It renders lines left aligned. Do you think its an upstream bug or we should care about it?

Best Regards,
Razi.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello Razi,

concerning the EPUB with RTL text problem:
* I think we should in any case treat it as an issue separate from this MR, so maybe a new bug is appropriate.
* I guess it is an upstream issue since EPUB is supposed to be HTML/CSS and some meta data and hence RTL should be handled as in HTML.
* But you should be able to test this by trying to open the same EPUB file directly using the MuPDF application and see whether it has the same problem.

Best regards,
Adam

Revision history for this message
Razi Alavizadeh (srazi) wrote :

Hello Adam,

I tested it using mupdf and it has the same issue. So it is an upstream bug.

Thanks,
Razi.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'fitz-plugin.pro'
2--- fitz-plugin.pro 2018-05-04 18:00:34 +0000
3+++ fitz-plugin.pro 2018-05-17 19:28:08 +0000
4@@ -21,7 +21,7 @@
5 DEFINES += $$FITZ_PLUGIN_DEFINES
6 INCLUDEPATH += $$FITZ_PLUGIN_INCLUDEPATH
7
8-isEmpty(FITZ_PLUGIN_LIBS):FITZ_PLUGIN_LIBS = -lmupdf -lfreetype -ljpeg -lz -lm -lcrypto
9+isEmpty(FITZ_PLUGIN_LIBS):FITZ_PLUGIN_LIBS = -lmupdf -lfreetype -ljpeg -lz -lm -lcrypto -lmujs -lgs -llcms2 -lopenjp2 -ljbig2dec
10 LIBS += $$FITZ_PLUGIN_LIBS
11
12 !static_fitz_plugin {
13
14=== modified file 'sources/fitzmodel.cpp'
15--- sources/fitzmodel.cpp 2017-04-19 21:01:25 +0000
16+++ sources/fitzmodel.cpp 2018-05-17 19:28:08 +0000
17@@ -28,10 +28,13 @@
18 extern "C"
19 {
20
21+#include <mupdf/fitz/stream.h>
22 #include <mupdf/fitz/bidi.h>
23 #include <mupdf/fitz/output.h>
24 #include <mupdf/fitz/display-list.h>
25 #include <mupdf/fitz/document.h>
26+#include <mupdf/fitz/pool.h>
27+#include <mupdf/fitz/structured-text.h>
28
29 typedef struct pdf_document_s pdf_document;
30
31@@ -42,9 +45,28 @@
32 namespace
33 {
34
35+const int maxSearchResultPerPage = 20;
36+
37 using namespace qpdfview;
38 using namespace qpdfview::Model;
39
40+QString externalLinkToString(char* uri)
41+{
42+ if (!uri)
43+ {
44+ return QString();
45+ }
46+
47+ QString url = QString::fromUtf8(uri);
48+
49+ if (url.toLower().startsWith("file://"))
50+ {
51+ url = url.mid(7);
52+ }
53+
54+ return url;
55+}
56+
57 Outline loadOutline(fz_outline* item)
58 {
59 Outline outline;
60@@ -55,9 +77,13 @@
61 Section& section = outline.back();
62 section.title = QString::fromUtf8(item->title);
63
64- if(item->dest.kind != FZ_LINK_NONE)
65- {
66- section.link.page = item->dest.ld.gotor.page + 1;
67+ if(item->page != -1)
68+ {
69+ section.link.page = item->page + 1;
70+ }
71+ else if (item->uri)
72+ {
73+ section.link.urlOrFileName = externalLinkToString(item->uri);
74 }
75
76 if(fz_outline* childItem = item->down)
77@@ -132,10 +158,11 @@
78
79
80 fz_context* context = fz_clone_context(m_parent->m_context);
81- fz_display_list* display_list = fz_new_display_list(context);
82+ fz_display_list* display_list = fz_new_display_list(context, &rect);
83
84 fz_device* device = fz_new_list_device(context, display_list);
85 fz_run_page(m_parent->m_context, m_page, device, &matrix, 0);
86+ fz_close_device(m_parent->m_context, device);
87 fz_drop_device(m_parent->m_context, device);
88
89
90@@ -154,20 +181,31 @@
91 {
92 fz_pre_translate(&tileMatrix, -boundingRect.x(), -boundingRect.y());
93
94- tileRect.x0 = tileRect.y0 = 0.0;
95-
96- tileWidth = tileRect.x1 = boundingRect.width();
97- tileHeight = tileRect.y1 = boundingRect.height();
98+ tileRect.x0 = boundingRect.x();
99+ tileRect.y0 = boundingRect.y();
100+
101+ tileRect.x1 = boundingRect.right();
102+ tileRect.y1 = boundingRect.bottom();
103+
104+ tileWidth = boundingRect.width();
105+ tileHeight = boundingRect.height();
106 }
107
108
109 QImage image(tileWidth, tileHeight, QImage::Format_RGB32);
110 image.fill(m_parent->m_paperColor);
111
112- fz_pixmap* pixmap = fz_new_pixmap_with_data(context, fz_device_bgr(context), image.width(), image.height(), image.bits());
113-
114- device = fz_new_draw_device(context, pixmap);
115- fz_run_display_list(context, display_list, device, &tileMatrix, &tileRect, 0);
116+ int imageWidth = image.width();
117+ fz_colorspace* colorSpace = fz_device_bgr(context);
118+
119+ int n = fz_colorspace_n(context, colorSpace);
120+ int stride = (n + 1) * imageWidth;
121+
122+ fz_pixmap* pixmap = fz_new_pixmap_with_data(context, colorSpace, imageWidth, image.height(), NULL, 1, stride, image.bits());
123+
124+ device = fz_new_draw_device(context, &tileMatrix, pixmap);
125+ fz_run_display_list(context, display_list, device, &fz_identity, &tileRect, NULL);
126+ fz_close_device(context, device);
127 fz_drop_device(context, device);
128
129 fz_drop_pixmap(context, pixmap);
130@@ -195,37 +233,24 @@
131 {
132 const QRectF boundary = QRectF(link->rect.x0 / width, link->rect.y0 / height, (link->rect.x1 - link->rect.x0) / width, (link->rect.y1 - link->rect.y0) / height).normalized();
133
134- if(link->dest.kind == FZ_LINK_GOTO)
135- {
136- const int page = link->dest.ld.gotor.page + 1;
137-
138- links.append(new Link(boundary, page));
139- }
140- else if(link->dest.kind == FZ_LINK_GOTOR)
141- {
142- const int page = link->dest.ld.gotor.page + 1;
143-
144- if(link->dest.ld.gotor.file_spec != 0)
145+ if (link->uri != NULL)
146+ {
147+ if (fz_is_external_link(m_parent->m_context, link->uri) == 0)
148 {
149- links.append(new Link(boundary, QString::fromUtf8(link->dest.ld.gotor.file_spec), page));
150+ float xp;
151+ float yp;
152+ const int page = fz_resolve_link(m_parent->m_context, m_parent->m_document, link->uri, &xp, &yp);
153+
154+ if (page != -1)
155+ {
156+ links.append(new Link(boundary, page + 1, xp / width, yp / height));
157+ }
158 }
159 else
160 {
161- links.append(new Link(boundary, page));
162+ links.append(new Link(boundary, externalLinkToString(link->uri)));
163 }
164 }
165- else if(link->dest.kind == FZ_LINK_URI)
166- {
167- const QString url = QString::fromUtf8(link->dest.ld.uri.uri);
168-
169- links.append(new Link(boundary, url));
170- }
171- else if(link->dest.kind == FZ_LINK_LAUNCH)
172- {
173- const QString url = QString::fromUtf8(link->dest.ld.launch.file_spec);
174-
175- links.append(new Link(boundary, url));
176- }
177 }
178
179 fz_drop_link(m_parent->m_context, first_link);
180@@ -233,6 +258,71 @@
181 return links;
182 }
183
184+QString FitzPage::text(const QRectF &rect) const
185+{
186+ QMutexLocker mutexLocker(&m_parent->m_mutex);
187+
188+ fz_rect mediabox;
189+ mediabox.x0 = rect.x();
190+ mediabox.y0 = rect.y();
191+ mediabox.x1 = rect.right();
192+ mediabox.y1 = rect.bottom();
193+
194+ fz_stext_page* stext_page = fz_new_stext_page(m_parent->m_context, &mediabox);
195+ fz_device* device = fz_new_stext_device(m_parent->m_context, stext_page, NULL);
196+ fz_run_page(m_parent->m_context, m_page, device, &fz_identity, NULL);
197+ fz_close_device(m_parent->m_context, device);
198+ fz_drop_device(m_parent->m_context, device);
199+
200+ fz_point topLeft;
201+ topLeft.x = rect.x();
202+ topLeft.y = rect.y();
203+
204+ fz_point bottomRight;
205+ bottomRight.x = rect.right();
206+ bottomRight.y = rect.bottom();
207+
208+ char* selection = fz_copy_selection(m_parent->m_context, stext_page, topLeft, bottomRight, 0);
209+ QString text = QString::fromUtf8(selection);
210+
211+ fz_drop_stext_page(m_parent->m_context, stext_page);
212+
213+ return text;
214+}
215+
216+QList<QRectF> FitzPage::search(const QString& text, bool matchCase, bool wholeWords) const
217+{
218+ Q_UNUSED(matchCase);
219+ Q_UNUSED(wholeWords);
220+
221+ QMutexLocker mutexLocker(&m_parent->m_mutex);
222+
223+ fz_rect rect;
224+ fz_bound_page(m_parent->m_context, m_page, &rect);
225+
226+ fz_stext_page* stext_page = fz_new_stext_page(m_parent->m_context, &rect);
227+ fz_device* device = fz_new_stext_device(m_parent->m_context, stext_page, NULL);
228+ fz_run_page(m_parent->m_context, m_page, device, &fz_identity, NULL);
229+ fz_close_device(m_parent->m_context, device);
230+ fz_drop_device(m_parent->m_context, device);
231+
232+ fz_rect rects[maxSearchResultPerPage];
233+
234+ int resultCount = fz_search_stext_page(m_parent->m_context, stext_page, text.toUtf8().constData(), rects, maxSearchResultPerPage);
235+
236+ fz_drop_stext_page(m_parent->m_context, stext_page);
237+
238+ QList< QRectF > results;
239+ results.reserve(resultCount);
240+
241+ for(int i = 0; i < resultCount; ++i)
242+ {
243+ results.append(QRectF(rects[i].x0, rects[i].y0, (rects[i].x1 - rects[i].x0), (rects[i].y1 - rects[i].y0)));
244+ }
245+
246+ return results;
247+}
248+
249 FitzDocument::FitzDocument(fz_context* context, fz_document* document) :
250 m_mutex(),
251 m_context(context),
252
253=== modified file 'sources/fitzmodel.h'
254--- sources/fitzmodel.h 2017-04-19 21:01:25 +0000
255+++ sources/fitzmodel.h 2018-05-17 19:28:08 +0000
256@@ -56,6 +56,10 @@
257
258 QList< Link* > links() const;
259
260+ QString text(const QRectF& rect) const;
261+
262+ QList< QRectF > search(const QString& text, bool matchCase, bool wholeWords) const;
263+
264 private:
265 Q_DISABLE_COPY(FitzPage)
266
267
268=== modified file 'sources/pluginhandler.cpp'
269--- sources/pluginhandler.cpp 2018-05-13 17:09:14 +0000
270+++ sources/pluginhandler.cpp 2018-05-17 19:28:08 +0000
271@@ -161,11 +161,42 @@
272 { "image/vnd.djvu", PluginHandler::DjVu, "djvu", "djv" },
273 { "application/x-gzip", PluginHandler::GZip, "gz", 0 },
274 { "application/x-bzip2", PluginHandler::BZip2, "bz2", 0 },
275- { "application/x-xz", PluginHandler::XZ, "xz", 0 }
276+ { "application/x-xz", PluginHandler::XZ, "xz", 0 },
277+ { "application/epub+zip", PluginHandler::EPUB, "epub", 0 },
278+ { "application/x-fictionbook+xml", PluginHandler::FB2, "fb2", 0 },
279+ { "application/x-zip-compressed-fb2", PluginHandler::FB2, "fb2", 0 },
280+ { "application/zip", PluginHandler::ZIP, "zip", 0 }
281 };
282
283 const MimeTypeMapping* const endOfMimeTypeMappings = mimeTypeMappings + sizeof(mimeTypeMappings) / sizeof(mimeTypeMappings[0]);
284
285+PluginHandler::FileType tryToDetectFileType(const QString& filePath, PluginHandler::FileType fileType, bool isImageFormat)
286+{
287+ if(fileType == PluginHandler::ZIP)
288+ {
289+ const QString suffix = QFileInfo(filePath).suffix().toLower();
290+
291+ if (suffix == "cbz")
292+ {
293+ fileType = PluginHandler::CBZ;
294+ }
295+ else if (suffix == "xps" || suffix == "oxps")
296+ {
297+ fileType = PluginHandler::XPS;
298+ }
299+ else
300+ {
301+ fileType = PluginHandler::Unknown;
302+ }
303+ }
304+ else if(fileType == PluginHandler::Unknown && isImageFormat)
305+ {
306+ fileType = PluginHandler::Image;
307+ }
308+
309+ return fileType;
310+}
311+
312 PluginHandler::FileType matchFileType(const QString& filePath)
313 {
314 PluginHandler::FileType fileType = PluginHandler::Unknown;
315@@ -183,10 +214,7 @@
316 }
317 }
318
319- if(fileType == PluginHandler::Unknown && isSupportedImageFormat(mimeType))
320- {
321- fileType = PluginHandler::Image;
322- }
323+ fileType = tryToDetectFileType(filePath, fileType, isSupportedImageFormat(mimeType));
324
325 if(fileType == PluginHandler::Unknown)
326 {
327@@ -212,10 +240,7 @@
328 }
329 }
330
331- if(fileType == PluginHandler::Unknown && isSupportedImageFormat(filePath))
332- {
333- fileType = PluginHandler::Image;
334- }
335+ fileType = tryToDetectFileType(filePath, fileType, isSupportedImageFormat(filePath));
336
337 if(fileType == PluginHandler::Unknown)
338 {
339@@ -238,10 +263,7 @@
340 }
341 }
342
343- if(fileType == PluginHandler::Unknown && isSupportedImageFormat(filePath))
344- {
345- fileType = PluginHandler::Image;
346- }
347+ fileType = tryToDetectFileType(filePath, fileType, isSupportedImageFormat(filePath));
348
349 if(fileType == PluginHandler::Unknown)
350 {
351@@ -376,6 +398,14 @@
352 return QLatin1String("DjVu");
353 case PluginHandler::Image:
354 return QLatin1String("Image");
355+ case PluginHandler::EPUB:
356+ return QLatin1String("EPUB");
357+ case PluginHandler::CBZ:
358+ return QLatin1String("CBZ");
359+ case PluginHandler::FB2:
360+ return QLatin1String("FictionBook2");
361+ case PluginHandler::XPS:
362+ return QLatin1String("XPS");
363 case PluginHandler::GZip:
364 case PluginHandler::BZip2:
365 case PluginHandler::XZ:
366@@ -395,6 +425,22 @@
367
368 #endif // WITH_PDF // WITH_FITZ
369
370+#if defined(WITH_FITZ)
371+
372+ openFilter.append(QLatin1String("EPUB (*.epub *.EPUB)"));
373+ supportedFormats.append(QLatin1String("*.epub *.EPUB"));
374+
375+ openFilter.append(QLatin1String("XPS (*.xps *.XPS *.oxps *.OXPS)"));
376+ supportedFormats.append(QLatin1String("*.xps *.XPS *.oxps *.OXPS"));
377+
378+ openFilter.append(QLatin1String("FictionBook 2 (*.fb2 *.FB2)"));
379+ supportedFormats.append(QLatin1String("*.fb2 *.FB2"));
380+
381+ openFilter.append(QLatin1String("CBZ (*.cbz *.CBZ)"));
382+ supportedFormats.append(QLatin1String("*.cbz *.CBZ"));
383+
384+#endif // WITH_FITZ
385+
386 #ifdef WITH_PS
387
388 openFilter.append(QLatin1String("PostScript (*.ps *.PS)"));
389@@ -500,8 +546,16 @@
390 #ifdef WITH_FITZ
391 #ifdef STATIC_FITZ_PLUGIN
392 m_objectNames.insertMulti(PDF, QLatin1String("FitzPlugin"));
393+ m_objectNames.insertMulti(EPUB, QLatin1String("FitzPlugin"));
394+ m_objectNames.insertMulti(XPS, QLatin1String("FitzPlugin"));
395+ m_objectNames.insertMulti(FB2, QLatin1String("FitzPlugin"));
396+ m_objectNames.insertMulti(CBZ, QLatin1String("FitzPlugin"));
397 #else
398 m_fileNames.insertMulti(PDF, QLatin1String(FITZ_PLUGIN_NAME));
399+ m_fileNames.insertMulti(EPUB, QLatin1String(FITZ_PLUGIN_NAME));
400+ m_fileNames.insertMulti(XPS, QLatin1String(FITZ_PLUGIN_NAME));
401+ m_fileNames.insertMulti(FB2, QLatin1String(FITZ_PLUGIN_NAME));
402+ m_fileNames.insertMulti(CBZ, QLatin1String(FITZ_PLUGIN_NAME));
403 #endif // STATIC_FITZ_PLUGIN
404 #endif // WITH_FITZ
405
406
407=== modified file 'sources/pluginhandler.h'
408--- sources/pluginhandler.h 2017-04-30 21:32:26 +0000
409+++ sources/pluginhandler.h 2018-05-17 19:28:08 +0000
410@@ -56,7 +56,12 @@
411 Image,
412 GZip,
413 BZip2,
414- XZ
415+ XZ,
416+ EPUB,
417+ FB2,
418+ ZIP,
419+ CBZ,
420+ XPS
421 };
422
423 static QLatin1String fileTypeName(FileType fileType);

Subscribers

People subscribed via source and target branches