Merge lp:~srazi/qpdfview/update-fitz-to-1-13 into lp:qpdfview
- update-fitz-to-1-13
- Merge into trunk
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 | ||||||||||||
Related bugs: |
|
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
Description of the change
Razi Alavizadeh (srazi) wrote : | # |
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.
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.
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
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
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); |
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.