From 62727f05297af2894d62630d387fb39e672b4451 Mon Sep 17 00:00:00 2001 From: ZackaryWelch Date: Fri, 30 Mar 2018 18:47:20 -0400 Subject: Added support for internal links. Code added for Poppler backend but not enabled. Code enabled for MuPDF. Does not support extrnal links yet. --- src-qt5/desktop-utils/lumina-pdf/PrintWidget.cpp | 21 ++- src-qt5/desktop-utils/lumina-pdf/PrintWidget.h | 66 ++++++++- .../desktop-utils/lumina-pdf/Renderer-mupdf.cpp | 151 ++++++++++++++------- .../desktop-utils/lumina-pdf/Renderer-poppler.cpp | 82 ++++++++++- src-qt5/desktop-utils/lumina-pdf/Renderer.h | 3 + 5 files changed, 270 insertions(+), 53 deletions(-) (limited to 'src-qt5/desktop-utils') diff --git a/src-qt5/desktop-utils/lumina-pdf/PrintWidget.cpp b/src-qt5/desktop-utils/lumina-pdf/PrintWidget.cpp index 013708ac..73e47091 100644 --- a/src-qt5/desktop-utils/lumina-pdf/PrintWidget.cpp +++ b/src-qt5/desktop-utils/lumina-pdf/PrintWidget.cpp @@ -131,7 +131,7 @@ void PrintWidget::setCurrentPage(int pageNumber) { void PrintWidget::highlightText(TextData *text) { //Creates a rectangle around the text if the text has not already been highlighted - if(!text->highlighted()) { + if(!text->highlighted() && !text->loc().isNull()) { int degrees = BACKEND->rotatedDegrees(); //Shows the text's location on a non-rotated page QRectF rect = text->loc(); @@ -224,6 +224,13 @@ void PrintWidget::populateScene() for (int i = 0; i < pages.size(); i++){ scene->removeItem(pages.at(i)); } + for(int i = 0; i < links.size(); i++) { + if(links[i].size() > 0) { + qDeleteAll(links[i]); + links[i].clear(); + } + } + links.clear(); qDeleteAll(pages); pages.clear(); int numPages = BACKEND->numPages(); @@ -231,16 +238,26 @@ void PrintWidget::populateScene() for (int i = 0; i < numPages; i++) { QImage pagePicture = BACKEND->imageHash(i); - QSize paperSize = pagePicture.size(); + QList linkLocations; if(pagePicture.isNull()) { qDebug() << "NULL IMAGE ON PAGE " << i; continue; } + PageItem* item = new PageItem(i+1, pagePicture, paperSize); scene->addItem(item); pages.append(item); + + if(BACKEND->supportsExtraFeatures()) { + for(int k = 0; k < BACKEND->linkSize(i); k++) { + LinkItem *lItem = new LinkItem(item, BACKEND->linkList(i, k)); + lItem->setOpacity(0.1); + linkLocations.append(lItem); + } + links.insert(i, linkLocations); + } } } diff --git a/src-qt5/desktop-utils/lumina-pdf/PrintWidget.h b/src-qt5/desktop-utils/lumina-pdf/PrintWidget.h index 92d82e11..3860b011 100644 --- a/src-qt5/desktop-utils/lumina-pdf/PrintWidget.h +++ b/src-qt5/desktop-utils/lumina-pdf/PrintWidget.h @@ -22,6 +22,33 @@ #include "Renderer.h" #include "TextData.h" +class LinkItem: public QGraphicsItem { +public: + LinkItem(QGraphicsItem *parent, TextData *_data) : QGraphicsItem(parent), bbox(_data->loc()), data(_data) { + setCacheMode(DeviceCoordinateCache); + setAcceptHoverEvents(true); + } + +QRectF boundingRect() const Q_DECL_OVERRIDE + { return bbox; } + +inline TextData* getData() const + { return data; } + +void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE +{ + Q_UNUSED(widget); + painter->setClipRect(option->exposedRect); + painter->setBrush(QBrush(QColor(255, 255, 177, 100))); + painter->setPen(QPen(QColor(255, 255, 100, 125))); + painter->drawRect(bbox); +} + +private: + QRectF bbox; + TextData *data; +}; + class PageItem : public QGraphicsItem { public: PageItem(int _pageNum, QImage _pagePicture, QSize _paperSize) @@ -30,6 +57,7 @@ public: brect = QRectF(QPointF(-25, -25), QSizeF(paperSize)+QSizeF(50, 50)); setCacheMode(DeviceCoordinateCache); + setAcceptHoverEvents(true); } QRectF boundingRect() const Q_DECL_OVERRIDE @@ -80,7 +108,6 @@ private: QRectF brect; }; - class PrintWidget : public QGraphicsView { Q_OBJECT @@ -112,6 +139,7 @@ private: double zoomFactor; bool initialized, fitting; QList pages; + QHash> links; int degrees; Renderer *BACKEND; @@ -158,6 +186,42 @@ protected: emit resized(); } + void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE { + QGraphicsView::mouseMoveEvent(e); + QGraphicsItem *item = scene->itemAt(mapToScene(e->pos()), transform()); + + if(item) { + LinkItem *link = dynamic_cast(item); + if(link) + link->setOpacity(1); + QList linkList; + if(PageItem *page = dynamic_cast(item)) + linkList = page->childItems(); + else + linkList = link->parentItem()->childItems(); + foreach(QGraphicsItem *linkItem, linkList) { + if(dynamic_cast(linkItem) == link) + continue; + dynamic_cast(linkItem)->setOpacity(0.1); + } + } + } + + void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE { + QGraphicsView::mouseReleaseEvent(e); + QPointF scenePoint = mapToScene(e->pos()); + QGraphicsItem *item = scene->itemAt(scenePoint, transform()); + if(LinkItem *link = dynamic_cast(item)) { + PageItem *page = dynamic_cast(link->parentItem()); + if(!BACKEND->isExternalLink(page->pageNumber()-1, link->getData()->text())) { + BACKEND->handleLink(link->getData()->text()); + }else{ + //Handle external link + } + link->setOpacity(0.1); + } + } + void showEvent(QShowEvent* e) Q_DECL_OVERRIDE { QGraphicsView::showEvent(e); emit resized(); diff --git a/src-qt5/desktop-utils/lumina-pdf/Renderer-mupdf.cpp b/src-qt5/desktop-utils/lumina-pdf/Renderer-mupdf.cpp index c36c6b08..9b2a43f3 100644 --- a/src-qt5/desktop-utils/lumina-pdf/Renderer-mupdf.cpp +++ b/src-qt5/desktop-utils/lumina-pdf/Renderer-mupdf.cpp @@ -1,4 +1,5 @@ -#include "Renderer.h" +#include "Renderer.h" +#include "TextData.h" #include #include #include @@ -6,28 +7,70 @@ #include #include +class Link { + public: + Link(fz_context *_ctx, fz_link *_fzLink, char *_uri, int _page, QRectF _loc = QRectF()) : fzLink(_fzLink), ctx(_ctx) { + QString uri = QString::fromLocal8Bit(_uri); + data = new TextData(_loc, _page, uri); + } + + ~Link() { + fz_drop_link(ctx, fzLink); + delete data; + } + + TextData* getData() { return data; } + + private: + TextData *data; + fz_link *fzLink; + fz_context *ctx; +}; + class Data { public: - Data(int _pagenum, fz_context *_ctx, fz_display_list *_list, fz_rect _bbox, fz_pixmap *_pix, fz_matrix _ctm, double _sf) : pagenumber(_pagenum), - ctx(_ctx), - list(_list), - bbox(_bbox), - pix(_pix), - ctm(_ctm), - sf(_sf) - { } + Data(int _pagenum, fz_context *_ctx, fz_display_list *_list, fz_rect _bbox, fz_matrix _ctm, double _sf, fz_link *_link) : pagenumber(_pagenum), ctx(_ctx), list(_list), bbox(_bbox), ctm(_ctm), sf(_sf) { - ~Data() { } + while(_link) { + QRectF rect(sf*_link->rect.x0, sf*_link->rect.y0, sf*(_link->rect.x1 - _link->rect.x0), sf*(_link->rect.y1 - _link->rect.y0)); + Link *link = new Link(_ctx, _link, _link->uri, _pagenum, rect); + linkList.push_back(link); + _link = _link->next; + } + } + + ~Data() { + fz_drop_pixmap(ctx, pix); + fz_drop_display_list(ctx, list); + qDeleteAll(linkList); + linkList.clear(); + } + int getPage() { return pagenumber; } + QList getLinkList() { return linkList; } + fz_context* getContext() { return ctx; } + fz_display_list* getDisplayList() { return list; } + QRectF getScaledRect() { return QRectF(sf*bbox.x0, sf*bbox.y0, sf*(bbox.x1-bbox.x0), sf*(bbox.y1 - bbox.y0)); } + fz_rect getBoundingBox() { return bbox; } + fz_matrix getMatrix() { return ctm; } + QImage getImage() { return img; } + + void setImage(QImage _img) { img = _img; } + void setRenderThread(QFuture thread) { renderThread = thread; } + void setPixmap(fz_pixmap *_pix) { pix = _pix; } + + private: int pagenumber; fz_context *ctx; fz_display_list *list; fz_rect bbox; - fz_pixmap *pix; fz_matrix ctm; + QList linkList; + double sf; + + fz_pixmap *pix; QImage img; QFuture renderThread; - double sf; }; fz_document *DOC; @@ -85,7 +128,8 @@ void Renderer::handleLink(QString link) { if(!link.isEmpty()) { pagenum = fz_resolve_link(CTX, DOC, uri, &xp, &yp); - emit goToPosition(pagenum+1, xp, yp); + if(pagenum != -1) + emit goToPosition(pagenum+1, xp, yp); } } @@ -174,29 +218,29 @@ bool Renderer::loadDocument(QString path, QString password){ void renderer(Data *data, Renderer *obj) { - int pagenum = data->pagenumber; + int pagenum = data->getPage(); //qDebug() << "Rendering:" << pagenum; - fz_context *ctx = data->ctx; - fz_display_list *list = data->list; - fz_rect bbox = data->bbox; - fz_pixmap *pixmap = data->pix; - fz_matrix ctm = data->ctm; + fz_context *ctx = data->getContext(); + fz_rect bbox = data->getBoundingBox(); + fz_matrix ctm = data->getMatrix(); fz_device *dev; + fz_irect rbox; ctx = fz_clone_context(ctx); + fz_pixmap *pixmap = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_round_rect(&rbox, &bbox), NULL, 0); + fz_clear_pixmap_with_value(ctx, pixmap, 0xff); + dev = fz_new_draw_device(ctx, &fz_identity, pixmap); - fz_run_display_list(ctx, list, dev, &ctm, &bbox, NULL); + fz_run_display_list(ctx, data->getDisplayList(), dev, &ctm, &bbox, NULL); + + data->setImage(QImage(pixmap->samples, pixmap->w, pixmap->h, pixmap->stride, QImage::Format_RGB888)); + data->setPixmap(pixmap); + dataHash.insert(pagenum, data); - data->img = QImage(pixmap->samples, pixmap->w, pixmap->h, pixmap->stride, QImage::Format_RGB888); fz_close_device(ctx, dev); fz_drop_device(ctx, dev); - fz_drop_context(ctx); - if(dataHash.find(pagenum) == dataHash.end()) - dataHash.insert(pagenum, data); - else - dataHash[pagenum] = data; emit obj->PageLoaded(pagenum); } @@ -205,8 +249,6 @@ void Renderer::renderPage(int pagenum, QSize DPI, int degrees){ Data *data; fz_matrix matrix; fz_rect bbox; - fz_irect rbox; - fz_pixmap *pixmap; fz_display_list *list; double pageDPI = 96.0; @@ -222,42 +264,39 @@ void Renderer::renderPage(int pagenum, QSize DPI, int degrees){ list = fz_new_display_list(CTX, &bbox); fz_device *dev = fz_new_list_device(CTX, list); fz_run_page(CTX, PAGE, dev, &fz_identity, NULL); - /*fz_annot *annot = fz_first_annot(CTX, PAGE); + fz_annot *annot = fz_first_annot(CTX, PAGE); while(annot) { - fz_run_annot(CTX, annot, dev, &fz_identity, NULL); - fz_rect bbox; - fz_bound_annot(CTX, annot, &bbox); + fz_rect anotBox; + fz_bound_annot(CTX, annot, &anotBox); + fz_run_annot(CTX, annot, dev, &matrix, NULL); - QRectF rect(bbox.x0, bbox.y0, (bbox.x1-bbox.x0), (bbox.y1 - bbox.y0)); + QRectF rect(anotBox.x0, anotBox.y0, (anotBox.x1-anotBox.x0), (anotBox.y1 - anotBox.y0)); qDebug() << "Annotation:" << rect << "at" << pagenum; annot = fz_next_annot(CTX, annot); - }*/ + } + fz_link *link = fz_load_links(CTX, PAGE); + fz_close_device(CTX, dev); fz_drop_device(CTX, dev); fz_drop_page(CTX, PAGE); - pixmap = fz_new_pixmap_with_bbox(CTX, fz_device_rgb(CTX), fz_round_rect(&rbox, &bbox), NULL, 0); - fz_clear_pixmap_with_value(CTX, pixmap, 0xff); - - data = new Data(pagenum, CTX, list, bbox, pixmap, matrix, sf); - data->renderThread = QtConcurrent::run(&renderer, data, this); + data = new Data(pagenum, CTX, list, bbox, matrix, sf, link); + data->setRenderThread(QtConcurrent::run(&renderer, data, this)); } QList Renderer::searchDocument(QString text, bool matchCase){ fz_rect rectBuffer[1000]; QList results; for(int i = 0; i < pnum; i++) { - int count = fz_search_display_list(CTX, dataHash[i]->list, text.toLocal8Bit().data(), rectBuffer, 1000); + int count = fz_search_display_list(CTX, dataHash[i]->getDisplayList(), text.toLocal8Bit().data(), rectBuffer, 1000); //qDebug() << "Page " << i+1 << ": Count, " << count; for(int j = 0; j < count; j++) { - double sf = dataHash[i]->sf; - QRectF rect(rectBuffer[j].x0*sf, rectBuffer[j].y0*sf, (rectBuffer[j].x1-rectBuffer[j].x0)*sf, (rectBuffer[j].y1 - rectBuffer[j].y0)*sf); - TextData *t = new TextData(rect, i+1, text); + TextData *t = new TextData(dataHash[i]->getScaledRect(), i+1, text); //MuPDF search does not match case, so retrieve the exact text at the location found and determine whether or not it matches the case of the search text if the user selected to match case if(matchCase){ - fz_stext_page *sPage = fz_new_stext_page_from_display_list(CTX, dataHash[i]->list, NULL); + fz_stext_page *sPage = fz_new_stext_page_from_display_list(CTX, dataHash[i]->getDisplayList(), NULL); QString currentStr = QString(fz_copy_selection(CTX, sPage, *fz_rect_min(&rectBuffer[j]), *fz_rect_max(&rectBuffer[j]), false)); if(currentStr.contains(text, Qt::CaseSensitive)){ results.append(t); } }else{ @@ -269,7 +308,7 @@ QList Renderer::searchDocument(QString text, bool matchCase){ } QImage Renderer::imageHash(int pagenum) { - return dataHash[pagenum]->img; + return dataHash[pagenum]->getImage(); } int Renderer::hashSize() { @@ -277,12 +316,26 @@ int Renderer::hashSize() { } void Renderer::clearHash() { - for(int i = 0; i < dataHash.size(); i++) { - fz_drop_pixmap(CTX, dataHash[i]->pix); - fz_drop_display_list(CTX, dataHash[i]->list); - } qDeleteAll(dataHash); dataHash.clear(); } +TextData* Renderer::linkList(int pagenum, int entry) { + return dataHash[pagenum]->getLinkList()[entry]->getData(); +} + +int Renderer::linkSize(int pagenum) { + return dataHash[pagenum]->getLinkList().size(); +} + +bool Renderer::isExternalLink(int pagenum, QString text) { + QList linkList = dataHash[pagenum]->getLinkList(); + foreach(Link *link, linkList) { + if(link->getData()->text() == text) + return fz_is_external_link(CTX, link->getData()->text().toLocal8Bit().data()); + } + return false; +} + bool Renderer::supportsExtraFeatures() { return true; } + diff --git a/src-qt5/desktop-utils/lumina-pdf/Renderer-poppler.cpp b/src-qt5/desktop-utils/lumina-pdf/Renderer-poppler.cpp index 7f2baa5e..98a351bd 100644 --- a/src-qt5/desktop-utils/lumina-pdf/Renderer-poppler.cpp +++ b/src-qt5/desktop-utils/lumina-pdf/Renderer-poppler.cpp @@ -2,8 +2,22 @@ #include #include +class Link { + public: + Link(TextData *_data, Poppler::Link *_link) : data(_data), link(_link) { } + ~Link() { delete data; } + + TextData* getData() { return data; } + Poppler::Link* getLink() { return link; } + + private: + TextData *data; + Poppler::Link *link; +}; + static Poppler::Document *DOC; QHash loadingHash; +QHash> linkHash; Renderer::Renderer(){ DOC = 0; @@ -29,6 +43,12 @@ bool Renderer::loadDocument(QString path, QString password){ //Clear out the old document first delete DOC; DOC=0; + if(linkHash.size() > 0) { + foreach(QList linkArray, linkHash) { + qDeleteAll(linkArray); + } + linkHash.clear(); + } needpass = false; pnum=0; docpath = path; @@ -91,6 +111,17 @@ void Renderer::renderPage(int pagenum, QSize DPI, int degrees){ } img = PAGE->renderToImage(DPI.width(), DPI.height(), -1, -1, -1, -1, rotation); loadingHash.insert(pagenum, img); + QList linkArray; + foreach(Poppler::Link *link, PAGE->links()) { + QString location; + if(link->linkType() == Poppler::Link::LinkType::Goto) + location = dynamic_cast(link)->fileName(); + else if(link->linkType() == Poppler::Link::LinkType::Goto) + location = dynamic_cast(link)->url(); + Link *newLink = new Link(new TextData(link->linkArea(), pagenum, location), link); + linkArray.append(newLink); + } + linkHash.insert(pagenum, linkArray); delete PAGE; } //qDebug() << "Done Render Page:" << pagenum << img.size(); @@ -132,4 +163,53 @@ bool Renderer::supportsExtraFeatures() { return false; } void Renderer::traverseOutline(void *, int) { } -void Renderer::handleLink(QString link) { } +void Renderer::handleLink(QString linkDest) { + Poppler::Link* trueLink; + foreach(QList linkArray, linkHash) { + for(int i = 0; i < linkArray.size(); i++) { + Poppler::Link* link = linkArray[i]->getLink(); + if(link->linkType() == Poppler::Link::LinkType::Browse) { + if(linkDest == dynamic_cast(link)->url()) + trueLink = link; + }else if(link->linkType() == Poppler::Link::LinkType::Goto) { + if(linkDest == dynamic_cast(link)->fileName()) + trueLink = link; + } + } + } + if(trueLink) { + if(trueLink->linkType() == Poppler::Link::LinkType::Goto) + emit goToPosition(dynamic_cast(trueLink)->destination().pageNumber(), 0, 0); + } +} + +TextData* Renderer::linkList(int pageNum, int entry) { + if(linkHash[pageNum].size() > 0) + return linkHash[pageNum][entry]->getData(); + else + return 0; +} + +int Renderer::linkSize(int pageNum) { return linkHash[pageNum].size(); } + +bool Renderer::isExternalLink(int pageNum, QString text) { + Poppler::Link* trueLink; + foreach(QList linkArray, linkHash) { + for(int i = 0; i < linkArray.size(); i++) { + Poppler::Link* link = linkArray[i]->getLink(); + if(link->linkType() == Poppler::Link::LinkType::Browse) { + if(text == dynamic_cast(link)->url()) + trueLink = link; + }else if(link->linkType() == Poppler::Link::LinkType::Goto) { + if(text == dynamic_cast(link)->fileName()) + trueLink = link; + } + } + } + if(trueLink) { + if(trueLink->linkType() == Poppler::Link::LinkType::Goto) + return dynamic_cast(trueLink)->isExternal(); + if(trueLink->linkType() == Poppler::Link::LinkType::Browse) + return true; + } +} diff --git a/src-qt5/desktop-utils/lumina-pdf/Renderer.h b/src-qt5/desktop-utils/lumina-pdf/Renderer.h index 22548278..cc48b089 100644 --- a/src-qt5/desktop-utils/lumina-pdf/Renderer.h +++ b/src-qt5/desktop-utils/lumina-pdf/Renderer.h @@ -46,6 +46,9 @@ public: QList searchDocument(QString text, bool matchCase); void traverseOutline(void *, int); void handleLink(QString); + TextData *linkList(int, int); + int linkSize(int); + bool isExternalLink(int, QString); void clearHash(); //Makes sure degrees is between 0 and 360 then rotates the matrix and -- cgit