1 /*
2
3 Copyright 2014 S. Razi Alavizadeh
4 Copyright 2018 Marshall Banana
5 Copyright 2013-2014, 2018 Adam Reichold
6 Copyright 2013 Alexander Volkov
7
8 This file is part of qpdfview.
9
10 The implementation is based on KDjVu by Pino Toscano.
11
12 qpdfview is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 2 of the License, or
15 (at your option) any later version.
16
17 qpdfview is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
24
25 */
26
27 #include "djvumodel.h"
28
29 #include <cstdio>
30
31 #include <QFile>
32 #include <QPainterPath>
33 #include <qmath.h>
34
35 #if defined(Q_OS_WIN) && defined(DJVU_STATIC)
36
37 #define DDJVUAPI /**/
38 #define MINILISPAPI /**/
39
40 #endif // Q_OS_WIN DJVU_STATIC
41
42 #include <libdjvu/ddjvuapi.h>
43 #include <libdjvu/miniexp.h>
44
45 #define LOCK_PAGE QMutexLocker mutexLocker(&m_parent->m_mutex);
46 #define LOCK_DOCUMENT QMutexLocker mutexLocker(&m_mutex);
47
48 #if DDJVUAPI_VERSION < 23
49
50 #define LOCK_PAGE_GLOBAL QMutexLocker globalMutexLocker(m_parent->m_globalMutex);
51 #define LOCK_DOCUMENT_GLOBAL QMutexLocker globalMutexLocker(m_globalMutex);
52
53 #else
54
55 #define LOCK_PAGE_GLOBAL
56 #define LOCK_DOCUMENT_GLOBAL
57
58 #endif // DDJVUAPI_VERSION
59
60 namespace
61 {
62
63 using namespace qpdfview;
64 using namespace qpdfview::Model;
65
miniexp_cadddr(miniexp_t exp)66 inline miniexp_t miniexp_cadddr(miniexp_t exp)
67 {
68 return miniexp_cadr(miniexp_cddr(exp));
69 }
70
miniexp_caddddr(miniexp_t exp)71 inline miniexp_t miniexp_caddddr(miniexp_t exp)
72 {
73 return miniexp_caddr(miniexp_cddr(exp));
74 }
75
skip(miniexp_t exp,int offset)76 inline miniexp_t skip(miniexp_t exp, int offset)
77 {
78 while(offset-- > 0)
79 {
80 exp = miniexp_cdr(exp);
81 }
82
83 return exp;
84 }
85
clearMessageQueue(ddjvu_context_t * context,bool wait)86 void clearMessageQueue(ddjvu_context_t* context, bool wait)
87 {
88 if(wait)
89 {
90 ddjvu_message_wait(context);
91 }
92
93 while(true)
94 {
95 if(ddjvu_message_peek(context) != 0)
96 {
97 ddjvu_message_pop(context);
98 }
99 else
100 {
101 break;
102 }
103 }
104 }
105
waitForMessageTag(ddjvu_context_t * context,ddjvu_message_tag_t tag)106 void waitForMessageTag(ddjvu_context_t* context, ddjvu_message_tag_t tag)
107 {
108 ddjvu_message_wait(context);
109
110 while(true)
111 {
112 ddjvu_message_t* message = ddjvu_message_peek(context);
113
114 if(message != 0)
115 {
116 if(message->m_any.tag == tag)
117 {
118 break;
119 }
120
121 ddjvu_message_pop(context);
122 }
123 else
124 {
125 break;
126 }
127 }
128 }
129
loadLinkBoundary(const QString & type,miniexp_t boundaryExp,QSizeF size)130 QPainterPath loadLinkBoundary(const QString& type, miniexp_t boundaryExp, QSizeF size)
131 {
132 QPainterPath boundary;
133
134 const int count = miniexp_length(boundaryExp);
135
136 if(count == 4 && (type == QLatin1String("rect") || type == QLatin1String("oval")))
137 {
138 QPoint p(miniexp_to_int(miniexp_car(boundaryExp)), miniexp_to_int(miniexp_cadr(boundaryExp)));
139 QSize s(miniexp_to_int(miniexp_caddr(boundaryExp)), miniexp_to_int(miniexp_cadddr(boundaryExp)));
140
141 p.setY(size.height() - s.height() - p.y());
142
143 const QRectF r(p, s);
144
145 if(type == QLatin1String("rect"))
146 {
147 boundary.addRect(r);
148 }
149 else
150 {
151 boundary.addEllipse(r);
152 }
153 }
154 else if(count > 0 && count % 2 == 0 && type == QLatin1String("poly"))
155 {
156 QPolygon polygon;
157
158 for(int index = 0; index < count; index += 2)
159 {
160 QPoint p(miniexp_to_int(miniexp_nth(index, boundaryExp)), miniexp_to_int(miniexp_nth(index + 1, boundaryExp)));
161
162 p.setY(size.height() - p.y());
163
164 polygon << p;
165 }
166
167 boundary.addPolygon(polygon);
168 }
169
170 return QTransform::fromScale(1.0 / size.width(), 1.0 / size.height()).map(boundary);
171 }
172
loadLinkTarget(const QPainterPath & boundary,miniexp_t targetExp,int index,const QHash<QString,int> & pageByName)173 Link* loadLinkTarget(const QPainterPath& boundary, miniexp_t targetExp, int index, const QHash< QString, int >& pageByName)
174 {
175 QString target;
176
177 if(miniexp_stringp(targetExp))
178 {
179 target = QString::fromUtf8(miniexp_to_str(targetExp));
180 }
181 else if(miniexp_length(targetExp) == 3 && qstrcmp(miniexp_to_name(miniexp_car(targetExp)), "url") == 0)
182 {
183 target = QString::fromUtf8(miniexp_to_str(miniexp_cadr(targetExp)));
184 }
185
186 if(target.isEmpty())
187 {
188 return 0;
189 }
190
191 if(target.at(0) == QLatin1Char('#'))
192 {
193 target.remove(0, 1);
194
195 bool ok = false;
196 int targetPage = target.toInt(&ok);
197
198 if(!ok)
199 {
200 const int page = pageByName.value(target);
201
202 if(page != 0)
203 {
204 targetPage = page;
205 }
206 else
207 {
208 return 0;
209 }
210 }
211 else
212 {
213 if(target.at(0) == QLatin1Char('+') || target.at(0) == QLatin1Char('-'))
214 {
215 targetPage += index + 1;
216 }
217 }
218
219 return new Link(boundary, targetPage);
220 }
221 else
222 {
223 return new Link(boundary, target);
224 }
225 }
226
loadLinks(miniexp_t linkExp,QSizeF size,int index,const QHash<QString,int> & pageByName)227 QList< Link* > loadLinks(miniexp_t linkExp, QSizeF size, int index, const QHash< QString, int >& pageByName)
228 {
229 QList< Link* > links;
230
231 for(miniexp_t linkItem = miniexp_nil; miniexp_consp(linkExp); linkExp = miniexp_cdr(linkExp))
232 {
233 linkItem = miniexp_car(linkExp);
234
235 if(miniexp_length(linkItem) < 4 || qstrcmp(miniexp_to_name(miniexp_car(linkItem)), "maparea") != 0)
236 {
237 continue;
238 }
239
240 miniexp_t targetExp = miniexp_cadr(linkItem);
241 miniexp_t boundaryExp = miniexp_cadddr(linkItem);
242
243 if(!miniexp_symbolp(miniexp_car(boundaryExp)))
244 {
245 continue;
246 }
247
248 const QString type = QString::fromUtf8(miniexp_to_name(miniexp_car(boundaryExp)));
249
250 if(type == QLatin1String("rect") || type == QLatin1String("oval") || type == QLatin1String("poly"))
251 {
252 QPainterPath boundary = loadLinkBoundary(type, miniexp_cdr(boundaryExp), size);
253
254 if(!boundary.isEmpty())
255 {
256 Link* link = loadLinkTarget(boundary, targetExp, index, pageByName);
257
258 if(link != 0)
259 {
260 links.append(link);
261 }
262 }
263 }
264 }
265
266 return links;
267 }
268
loadText(miniexp_t textExp,QSizeF size,const QRectF & rect)269 QString loadText(miniexp_t textExp, QSizeF size, const QRectF& rect)
270 {
271 if(miniexp_length(textExp) < 6 && !miniexp_symbolp(miniexp_car(textExp)))
272 {
273 return QString();
274 }
275
276 const int xmin = miniexp_to_int(miniexp_cadr(textExp));
277 const int ymin = miniexp_to_int(miniexp_caddr(textExp));
278 const int xmax = miniexp_to_int(miniexp_cadddr(textExp));
279 const int ymax = miniexp_to_int(miniexp_caddddr(textExp));
280
281 if(rect.intersects(QRect(xmin, size.height() - ymax, xmax - xmin, ymax - ymin)))
282 {
283 const QString type = QString::fromUtf8(miniexp_to_name(miniexp_car(textExp)));
284
285 if(type == QLatin1String("word"))
286 {
287 return QString::fromUtf8(miniexp_to_str(miniexp_nth(5, textExp)));
288 }
289 else
290 {
291 QStringList text;
292
293 textExp = skip(textExp, 5);
294
295 for(miniexp_t textItem = miniexp_nil; miniexp_consp(textExp); textExp = miniexp_cdr(textExp))
296 {
297 textItem = miniexp_car(textExp);
298
299 text.append(loadText(textItem, size, rect));
300 }
301
302 return type == QLatin1String("line") ? text.join(" ") : text.join("\n");
303 }
304 }
305
306 return QString();
307 }
308
findText(miniexp_t pageTextExp,QSizeF size,const QTransform & transform,const QStringList & words,bool matchCase,bool wholeWords)309 QList< QRectF > findText(miniexp_t pageTextExp, QSizeF size, const QTransform& transform, const QStringList& words, bool matchCase, bool wholeWords)
310 {
311 if(words.isEmpty())
312 {
313 return QList< QRectF >();
314 }
315
316 const Qt::CaseSensitivity caseSensitivity = matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive;
317
318 QRectF result;
319 int wordIndex = 0;
320
321 QList< miniexp_t > texts;
322 QList< QRectF > results;
323
324 texts.append(pageTextExp);
325
326 while(!texts.isEmpty())
327 {
328 miniexp_t textExp = texts.takeFirst();
329
330 if(miniexp_length(textExp) < 6 || !miniexp_symbolp(miniexp_car(textExp)))
331 {
332 continue;
333 }
334
335 const QString type = QString::fromUtf8(miniexp_to_name(miniexp_car(textExp)));
336
337 if(type == QLatin1String("word"))
338 {
339 const QString text = QString::fromUtf8(miniexp_to_str(miniexp_nth(5, textExp)));
340
341 int index = 0;
342
343 while((index = text.indexOf(words.at(wordIndex), index, caseSensitivity)) != -1)
344 {
345 const int nextIndex = index + words.at(wordIndex).length();
346
347 const bool wordBegins = index == 0 || !text.at(index - 1).isLetterOrNumber();
348 const bool wordEnds = nextIndex == text.length() || !text.at(nextIndex).isLetterOrNumber();
349
350 if(!wholeWords || (wordBegins && wordEnds))
351 {
352 const int xmin = miniexp_to_int(miniexp_cadr(textExp));
353 const int ymin = miniexp_to_int(miniexp_caddr(textExp));
354 const int xmax = miniexp_to_int(miniexp_cadddr(textExp));
355 const int ymax = miniexp_to_int(miniexp_caddddr(textExp));
356
357 result = result.united(QRectF(xmin, size.height() - ymax, xmax - xmin, ymax - ymin));
358
359 // Advance after partial match
360 if(++wordIndex == words.size())
361 {
362 results.append(transform.mapRect(result));
363
364 // Reset after full match
365 result = QRectF();
366 wordIndex = 0;
367 }
368 }
369 else
370 {
371 // Reset after malformed match
372 result = QRectF();
373 wordIndex = 0;
374 }
375
376 if((index = nextIndex) >= text.length())
377 {
378 break;
379 }
380 }
381
382 if(index < 0)
383 {
384 // Reset after empty match
385 result = QRectF();
386 wordIndex = 0;
387 }
388 }
389 else
390 {
391 textExp = skip(textExp, 5);
392
393 for(miniexp_t textItem = miniexp_nil; miniexp_consp(textExp); textExp = miniexp_cdr(textExp))
394 {
395 textItem = miniexp_car(textExp);
396
397 texts.append(textItem);
398 }
399 }
400 }
401
402 return results;
403 }
404
loadOutline(miniexp_t outlineExp,const QHash<QString,int> & pageByName)405 Outline loadOutline(miniexp_t outlineExp, const QHash< QString, int >& pageByName)
406 {
407 Outline outline;
408
409 for(miniexp_t outlineItem = miniexp_nil; miniexp_consp(outlineExp); outlineExp = miniexp_cdr(outlineExp))
410 {
411 outlineItem = miniexp_car(outlineExp);
412
413 if(miniexp_length(outlineItem) < 2 || !miniexp_stringp(miniexp_car(outlineItem)) || !miniexp_stringp(miniexp_cadr(outlineItem)))
414 {
415 continue;
416 }
417
418 const QString title = QString::fromUtf8(miniexp_to_str(miniexp_car(outlineItem)));
419
420 if(title.isEmpty())
421 {
422 continue;
423 }
424
425 outline.push_back(Section());
426 Section& section = outline.back();
427 section.title = title;
428
429 QString destination = QString::fromUtf8(miniexp_to_str(miniexp_cadr(outlineItem)));
430
431 if(!destination.isEmpty() && destination.at(0) == QLatin1Char('#'))
432 {
433 destination.remove(0, 1);
434
435 bool ok = false;
436 int page = destination.toInt(&ok);
437
438 if(!ok)
439 {
440 const int destinationPage = pageByName.value(destination);
441
442 if(destinationPage != 0)
443 {
444 ok = true;
445 page = destinationPage;
446 }
447 }
448
449 if(ok)
450 {
451 section.link.page = page;
452 }
453 }
454
455 if(miniexp_length(outlineItem) > 2)
456 {
457 section.children = loadOutline(skip(outlineItem, 2), pageByName);
458 }
459 }
460
461 return outline;
462 }
463
loadProperties(miniexp_t annoExp)464 Properties loadProperties(miniexp_t annoExp)
465 {
466 Properties properties;
467
468 for(miniexp_t annoItem = miniexp_nil; miniexp_consp(annoExp); annoExp = miniexp_cdr(annoExp))
469 {
470 annoItem = miniexp_car(annoExp);
471
472 if(miniexp_length(annoItem) < 2 || qstrcmp(miniexp_to_name(miniexp_car(annoItem)), "metadata") != 0)
473 {
474 continue;
475 }
476
477 annoItem = miniexp_cdr(annoItem);
478
479 for(miniexp_t keyValueItem = miniexp_nil; miniexp_consp(annoItem); annoItem = miniexp_cdr(annoItem))
480 {
481 keyValueItem = miniexp_car(annoItem);
482
483 if(miniexp_length(keyValueItem) != 2)
484 {
485 continue;
486 }
487
488 const QString key = QString::fromUtf8(miniexp_to_name(miniexp_car(keyValueItem)));
489 const QString value = QString::fromUtf8(miniexp_to_str(miniexp_cadr(keyValueItem)));
490
491 if(!key.isEmpty() && !value.isEmpty())
492 {
493 properties.push_back(qMakePair(key, value));
494 }
495 }
496 }
497
498 return properties;
499 }
500
501 } // anonymous
502
503 namespace qpdfview
504 {
505
506 namespace Model
507 {
508
DjVuPage(const DjVuDocument * parent,int index,const ddjvu_pageinfo_t & pageinfo)509 DjVuPage::DjVuPage(const DjVuDocument* parent, int index, const ddjvu_pageinfo_t& pageinfo) :
510 m_parent(parent),
511 m_index(index),
512 m_size(pageinfo.width, pageinfo.height),
513 m_resolution(pageinfo.dpi)
514 {
515 }
516
~DjVuPage()517 DjVuPage::~DjVuPage()
518 {
519 }
520
size() const521 QSizeF DjVuPage::size() const
522 {
523 return 72.0 / m_resolution * m_size;
524 }
525
render(qreal horizontalResolution,qreal verticalResolution,Rotation rotation,QRect boundingRect) const526 QImage DjVuPage::render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, QRect boundingRect) const
527 {
528 LOCK_PAGE
529
530 ddjvu_page_t* page = ddjvu_page_create_by_pageno(m_parent->m_document, m_index);
531
532 if(page == 0)
533 {
534 return QImage();
535 }
536
537 ddjvu_status_t status;
538
539 while(true)
540 {
541 status = ddjvu_page_decoding_status(page);
542
543 if(status < DDJVU_JOB_OK)
544 {
545 clearMessageQueue(m_parent->m_context, true);
546 }
547 else
548 {
549 break;
550 }
551 }
552
553 if(status >= DDJVU_JOB_FAILED)
554 {
555 ddjvu_page_release(page);
556
557 return QImage();
558 }
559
560 switch(rotation)
561 {
562 default:
563 case RotateBy0:
564 ddjvu_page_set_rotation(page, DDJVU_ROTATE_0);
565 break;
566 case RotateBy90:
567 ddjvu_page_set_rotation(page, DDJVU_ROTATE_270);
568 break;
569 case RotateBy180:
570 ddjvu_page_set_rotation(page, DDJVU_ROTATE_180);
571 break;
572 case RotateBy270:
573 ddjvu_page_set_rotation(page, DDJVU_ROTATE_90);
574 break;
575 }
576
577 ddjvu_rect_t pagerect;
578
579 pagerect.x = 0;
580 pagerect.y = 0;
581
582 switch(rotation)
583 {
584 default:
585 case RotateBy0:
586 case RotateBy180:
587 pagerect.w = qRound(horizontalResolution / m_resolution * m_size.width());
588 pagerect.h = qRound(verticalResolution / m_resolution * m_size.height());
589 break;
590 case RotateBy90:
591 case RotateBy270:
592 pagerect.w = qRound(horizontalResolution / m_resolution * m_size.height());
593 pagerect.h = qRound(verticalResolution / m_resolution * m_size.width());
594 break;
595 }
596
597 ddjvu_rect_t renderrect;
598
599 if(boundingRect.isNull())
600 {
601 renderrect.x = pagerect.x;
602 renderrect.y = pagerect.y;
603 renderrect.w = pagerect.w;
604 renderrect.h = pagerect.h;
605 }
606 else
607 {
608 renderrect.x = boundingRect.x();
609 renderrect.y = boundingRect.y();
610 renderrect.w = boundingRect.width();
611 renderrect.h = boundingRect.height();
612 }
613
614 QImage image(renderrect.w, renderrect.h, QImage::Format_RGB32);
615
616 if(!ddjvu_page_render(page, DDJVU_RENDER_COLOR, &pagerect, &renderrect, m_parent->m_format, image.bytesPerLine(), reinterpret_cast< char* >(image.bits())))
617 {
618 image = QImage();
619 }
620
621 clearMessageQueue(m_parent->m_context, false);
622
623 ddjvu_page_release(page);
624
625 return image;
626 }
627
label() const628 QString DjVuPage::label() const
629 {
630 return m_parent->m_titleByIndex.value(m_index);
631 }
632
links() const633 QList< Link* > DjVuPage::links() const
634 {
635 LOCK_PAGE
636
637 miniexp_t pageAnnoExp = miniexp_nil;
638
639 {
640 LOCK_PAGE_GLOBAL
641
642 while(true)
643 {
644 pageAnnoExp = ddjvu_document_get_pageanno(m_parent->m_document, m_index);
645
646 if(pageAnnoExp == miniexp_dummy)
647 {
648 clearMessageQueue(m_parent->m_context, true);
649 }
650 else
651 {
652 break;
653 }
654 }
655 }
656
657 const QList< Link* > links = loadLinks(pageAnnoExp, m_size, m_index, m_parent->m_pageByName);
658
659 {
660 LOCK_PAGE_GLOBAL
661
662 ddjvu_miniexp_release(m_parent->m_document, pageAnnoExp);
663 }
664
665 return links;
666 }
667
text(const QRectF & rect) const668 QString DjVuPage::text(const QRectF& rect) const
669 {
670 LOCK_PAGE
671
672 miniexp_t pageTextExp = miniexp_nil;
673
674 {
675 LOCK_PAGE_GLOBAL
676
677 while(true)
678 {
679 pageTextExp = ddjvu_document_get_pagetext(m_parent->m_document, m_index, "word");
680
681 if(pageTextExp == miniexp_dummy)
682 {
683 clearMessageQueue(m_parent->m_context, true);
684 }
685 else
686 {
687 break;
688 }
689 }
690 }
691
692 const QTransform transform = QTransform::fromScale(m_resolution / 72.0, m_resolution / 72.0);
693
694 const QString text = loadText(pageTextExp, m_size, transform.mapRect(rect)).simplified();
695
696 {
697 LOCK_PAGE_GLOBAL
698
699 ddjvu_miniexp_release(m_parent->m_document, pageTextExp);
700 }
701
702 return text.simplified();
703 }
704
search(const QString & text,bool matchCase,bool wholeWords) const705 QList< QRectF > DjVuPage::search(const QString& text, bool matchCase, bool wholeWords) const
706 {
707 LOCK_PAGE
708
709 miniexp_t pageTextExp = miniexp_nil;
710
711 {
712 LOCK_PAGE_GLOBAL
713
714 while(true)
715 {
716 pageTextExp = ddjvu_document_get_pagetext(m_parent->m_document, m_index, "word");
717
718 if(pageTextExp == miniexp_dummy)
719 {
720 clearMessageQueue(m_parent->m_context, true);
721 }
722 else
723 {
724 break;
725 }
726 }
727 }
728
729 const QTransform transform = QTransform::fromScale(72.0 / m_resolution, 72.0 / m_resolution);
730 const QStringList words = text.split(QRegExp(QLatin1String("\\W+")), QString::SkipEmptyParts);
731
732 const QList< QRectF > results = findText(pageTextExp, m_size, transform, words, matchCase, wholeWords);
733
734 {
735 LOCK_PAGE_GLOBAL
736
737 ddjvu_miniexp_release(m_parent->m_document, pageTextExp);
738 }
739
740 return results;
741 }
742
DjVuDocument(QMutex * globalMutex,ddjvu_context_t * context,ddjvu_document_t * document)743 DjVuDocument::DjVuDocument(QMutex* globalMutex, ddjvu_context_t* context, ddjvu_document_t* document) :
744 m_mutex(),
745 m_globalMutex(globalMutex),
746 m_context(context),
747 m_document(document),
748 m_format(0),
749 m_pageByName(),
750 m_titleByIndex()
751 {
752 unsigned int mask[] = {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000};
753
754 m_format = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 4, mask);
755 ddjvu_format_set_row_order(m_format, 1);
756 ddjvu_format_set_y_direction(m_format, 1);
757
758 prepareFileInfo();
759 }
760
~DjVuDocument()761 DjVuDocument::~DjVuDocument()
762 {
763 ddjvu_document_release(m_document);
764 ddjvu_context_release(m_context);
765 ddjvu_format_release(m_format);
766 }
767
numberOfPages() const768 int DjVuDocument::numberOfPages() const
769 {
770 LOCK_DOCUMENT
771
772 return ddjvu_document_get_pagenum(m_document);
773 }
774
page(int index) const775 Page* DjVuDocument::page(int index) const
776 {
777 LOCK_DOCUMENT
778
779 ddjvu_status_t status;
780 ddjvu_pageinfo_t pageinfo;
781
782 while(true)
783 {
784 status = ddjvu_document_get_pageinfo(m_document, index, &pageinfo);
785
786 if(status < DDJVU_JOB_OK)
787 {
788 clearMessageQueue(m_context, true);
789 }
790 else
791 {
792 break;
793 }
794 }
795
796 if(status >= DDJVU_JOB_FAILED)
797 {
798 return 0;
799 }
800
801 return new DjVuPage(this, index, pageinfo);
802 }
803
saveFilter() const804 QStringList DjVuDocument::saveFilter() const
805 {
806 return QStringList() << QLatin1String("DjVu (*.djvu *.djv)");
807 }
808
canSave() const809 bool DjVuDocument::canSave() const
810 {
811 return true;
812 }
813
save(const QString & filePath,bool withChanges) const814 bool DjVuDocument::save(const QString& filePath, bool withChanges) const
815 {
816 Q_UNUSED(withChanges);
817
818 LOCK_DOCUMENT
819
820 #ifdef _MSC_VER
821
822 FILE* file = _wfopen(reinterpret_cast< const wchar_t* >(filePath.utf16()), L"w+");
823
824 #else
825
826 FILE* file = fopen(QFile::encodeName(filePath), "w+");
827
828 #endif // _MSC_VER
829
830 if(file == 0)
831 {
832 return false;
833 }
834
835 ddjvu_job_t* job = ddjvu_document_save(m_document, file, 0, 0);
836
837 while(!ddjvu_job_done(job))
838 {
839 clearMessageQueue(m_context, true);
840 }
841
842 fclose(file);
843
844 return !ddjvu_job_error(job);
845 }
846
outline() const847 Outline DjVuDocument::outline() const
848 {
849 Outline outline;
850
851 LOCK_DOCUMENT
852
853 miniexp_t outlineExp = miniexp_nil;
854
855 {
856 LOCK_DOCUMENT_GLOBAL
857
858 while(true)
859 {
860 outlineExp = ddjvu_document_get_outline(m_document);
861
862 if(outlineExp == miniexp_dummy)
863 {
864 clearMessageQueue(m_context, true);
865 }
866 else
867 {
868 break;
869 }
870 }
871 }
872
873 if(miniexp_length(outlineExp) > 1 && qstrcmp(miniexp_to_name(miniexp_car(outlineExp)), "bookmarks") == 0)
874 {
875 outline = loadOutline(skip(outlineExp, 1), m_pageByName);
876 }
877
878 {
879 LOCK_DOCUMENT_GLOBAL
880
881 ddjvu_miniexp_release(m_document, outlineExp);
882 }
883
884 return outline;
885 }
886
properties() const887 Properties DjVuDocument::properties() const
888 {
889 Properties properties;
890
891 LOCK_DOCUMENT
892
893 miniexp_t annoExp = miniexp_nil;
894
895 {
896 LOCK_DOCUMENT_GLOBAL
897
898 while(true)
899 {
900 annoExp = ddjvu_document_get_anno(m_document, TRUE);
901
902 if(annoExp == miniexp_dummy)
903 {
904 clearMessageQueue(m_context, true);
905 }
906 else
907 {
908 break;
909 }
910 }
911 }
912
913 properties = loadProperties(annoExp);
914
915 {
916 LOCK_DOCUMENT_GLOBAL
917
918 ddjvu_miniexp_release(m_document, annoExp);
919 }
920
921 return properties;
922 }
923
prepareFileInfo()924 void DjVuDocument::prepareFileInfo()
925 {
926 for(int index = 0, count = ddjvu_document_get_filenum(m_document); index < count; ++index)
927 {
928 ddjvu_fileinfo_t fileinfo;
929
930 if(ddjvu_document_get_fileinfo(m_document, index, &fileinfo) != DDJVU_JOB_OK || fileinfo.type != 'P')
931 {
932 continue;
933 }
934
935 const QString id = QString::fromUtf8(fileinfo.id);
936 const QString name = QString::fromUtf8(fileinfo.name);
937 const QString title = QString::fromUtf8(fileinfo.title);
938
939 m_pageByName[id] = m_pageByName[name] = m_pageByName[title] = fileinfo.pageno + 1;
940
941 if(!title.endsWith(".djvu", Qt::CaseInsensitive) && !title.endsWith(".djv", Qt::CaseInsensitive))
942 {
943 m_titleByIndex[fileinfo.pageno] = title;
944 }
945 }
946
947 m_pageByName.squeeze();
948 m_titleByIndex.squeeze();
949 }
950
951 } // Model
952
DjVuPlugin(QObject * parent)953 DjVuPlugin::DjVuPlugin(QObject* parent) : QObject(parent),
954 m_globalMutex()
955 {
956 setObjectName("DjVuPlugin");
957 }
958
loadDocument(const QString & filePath) const959 Model::Document* DjVuPlugin::loadDocument(const QString& filePath) const
960 {
961 ddjvu_context_t* context = ddjvu_context_create("qpdfview");
962
963 if(context == 0)
964 {
965 return 0;
966 }
967
968 #if DDJVUAPI_VERSION >= 19
969
970 ddjvu_document_t* document = ddjvu_document_create_by_filename_utf8(context, filePath.toUtf8(), FALSE);
971
972 #else
973
974 ddjvu_document_t* document = ddjvu_document_create_by_filename(context, QFile::encodeName(filePath), FALSE);
975
976 #endif // DDJVUAPI_VERSION
977
978 if(document == 0)
979 {
980 ddjvu_context_release(context);
981
982 return 0;
983 }
984
985 waitForMessageTag(context, DDJVU_DOCINFO);
986
987 if(ddjvu_document_decoding_error(document))
988 {
989 ddjvu_document_release(document);
990 ddjvu_context_release(context);
991
992 return 0;
993 }
994
995 return new Model::DjVuDocument(&m_globalMutex, context, document);
996 }
997
998 } // qpdfview
999
1000 #if QT_VERSION < QT_VERSION_CHECK(5,0,0)
1001
1002 Q_EXPORT_PLUGIN2(qpdfview_djvu, qpdfview::DjVuPlugin)
1003
1004 #endif // QT_VERSION
1005