From efca4a3cdef157a099d52aeac63ec09303b7a99f Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 1 Feb 2020 17:28:02 +0100 Subject: [PATCH] New feature: rotating pages in viewer --- .../sources/pdfdrawspacecontroller.cpp | 112 ++++++++++++++---- PdfForQtLib/sources/pdfdrawspacecontroller.h | 31 +++-- PdfForQtLib/sources/pdfpage.cpp | 5 + PdfForQtLib/sources/pdfpage.h | 35 ++++++ PdfForQtViewer/pdfforqtviewer.qrc | 2 + PdfForQtViewer/pdfviewermainwindow.cpp | 12 ++ PdfForQtViewer/pdfviewermainwindow.h | 4 + PdfForQtViewer/pdfviewermainwindow.ui | 21 ++++ PdfForQtViewer/resources/rotate-left.svg | 84 +++++++++++++ PdfForQtViewer/resources/rotate-right.svg | 84 +++++++++++++ 10 files changed, 356 insertions(+), 34 deletions(-) create mode 100644 PdfForQtViewer/resources/rotate-left.svg create mode 100644 PdfForQtViewer/resources/rotate-right.svg diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp index 17c1bee..7919f81 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp @@ -37,6 +37,7 @@ PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) : m_pageLayoutMode(PageLayout::OneColumn), m_verticalSpacingMM(5.0), m_horizontalSpacingMM(1.0), + m_pageRotation(PageRotation::None), m_fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT) { @@ -137,6 +138,15 @@ QSizeF PDFDrawSpaceController::getReferenceBoundingBox() const return rect.size(); } +void PDFDrawSpaceController::setPageRotation(PageRotation pageRotation) +{ + if (m_pageRotation != pageRotation) + { + m_pageRotation = pageRotation; + recalculate(); + } +} + void PDFDrawSpaceController::recalculate() { if (!m_document) @@ -148,19 +158,6 @@ void PDFDrawSpaceController::recalculate() const PDFCatalog* catalog = m_document->getCatalog(); size_t pageCount = catalog->getPageCount(); - // First, preserve page rotations. We assume the count of pages is the same as the document. - // Document should not be changed while viewing. If a new document is setted, then the draw - // space is cleared first. - std::vector pageRotation(pageCount, PageRotation::None); - for (size_t i = 0; i < pageCount; ++i) - { - pageRotation[i] = catalog->getPage(i)->getPageRotation(); - } - for (const LayoutItem& layoutItem : m_layoutItems) - { - pageRotation[layoutItem.pageIndex] = layoutItem.pageRotation; - } - // Clear the old draw space clear(false); @@ -168,26 +165,26 @@ void PDFDrawSpaceController::recalculate() // Places the pages on the left/right sides. Pages can be nullptr, but not both of them. // Updates bounding rectangle. - auto placePagesLeftRight = [this, catalog, &pageRotation](PDFInteger blockIndex, size_t leftIndex, size_t rightIndex, PDFReal& yPos, QRectF& boundingRect) + auto placePagesLeftRight = [this, catalog](PDFInteger blockIndex, size_t leftIndex, size_t rightIndex, PDFReal& yPos, QRectF& boundingRect) { PDFReal yPosAdvance = 0.0; if (leftIndex != INVALID_PAGE_INDEX) { - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(leftIndex)->getMediaBoxMM(), pageRotation[leftIndex]).size(); + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(leftIndex)->getRotatedMediaBoxMM(), m_pageRotation).size(); PDFReal xPos = -pageSize.width() - m_horizontalSpacingMM * 0.5; QRectF rect(xPos, yPos, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(blockIndex, leftIndex, pageRotation[leftIndex], rect); + m_layoutItems.emplace_back(blockIndex, leftIndex, rect); yPosAdvance = qMax(yPosAdvance, pageSize.height()); boundingRect = boundingRect.united(rect); } if (rightIndex != INVALID_PAGE_INDEX) { - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(rightIndex)->getMediaBoxMM(), pageRotation[rightIndex]).size(); + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(rightIndex)->getRotatedMediaBoxMM(), m_pageRotation).size(); PDFReal xPos = m_horizontalSpacingMM * 0.5; QRectF rect(xPos, yPos, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(blockIndex, rightIndex, pageRotation[rightIndex], rect); + m_layoutItems.emplace_back(blockIndex, rightIndex, rect); yPosAdvance = qMax(yPosAdvance, pageSize.height()); boundingRect = boundingRect.united(rect); } @@ -247,9 +244,9 @@ void PDFDrawSpaceController::recalculate() for (size_t i = 0; i < pageCount; ++i) { - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getMediaBoxMM(), pageRotation[i]).size(); + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getRotatedMediaBoxMM(), m_pageRotation).size(); QRectF rect(-pageSize.width() * 0.5, -pageSize.height() * 0.5, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(i, i, pageRotation[i], rect); + m_layoutItems.emplace_back(i, i, rect); m_blockItems.emplace_back(rect); } @@ -268,9 +265,9 @@ void PDFDrawSpaceController::recalculate() for (size_t i = 0; i < pageCount; ++i) { // Top of current page is at yPos. - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getMediaBoxMM(), pageRotation[i]).size(); + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getRotatedMediaBoxMM(), m_pageRotation).size(); QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(0, i, pageRotation[i], rect); + m_layoutItems.emplace_back(0, i, rect); yPos += pageSize.height() + m_verticalSpacingMM; boundingRectangle = boundingRectangle.united(rect); } @@ -491,7 +488,7 @@ void PDFDrawWidgetProxy::update() m_layout.items.reserve(items.size()); for (const PDFDrawSpaceController::LayoutItem& item : items) { - m_layout.items.emplace_back(item.pageIndex, item.pageRotation, fromDeviceSpace(item.pageRectMM).toRect()); + m_layout.items.emplace_back(item.pageIndex, fromDeviceSpace(item.pageRectMM).toRect()); } m_layout.blockRect = fromDeviceSpace(rectangle).toRect(); @@ -594,6 +591,59 @@ void PDFDrawWidgetProxy::update() emit drawSpaceChanged(); } +QMatrix PDFDrawWidgetProxy::createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const +{ + QMatrix matrix; + + // We want to create transformation from unrotated rectangle + // to rotated page rectangle. + + QRectF unrotatedRectangle = rectangle; + switch (m_controller->getPageRotation()) + { + case PageRotation::None: + break; + + case PageRotation::Rotate180: + { + matrix.translate(0, rectangle.top() + rectangle.bottom()); + matrix.scale(1.0, -1.0); + Q_ASSERT(qFuzzyCompare(matrix.map(rectangle.topLeft()).y(), rectangle.bottom())); + Q_ASSERT(qFuzzyCompare(matrix.map(rectangle.bottomLeft()).y(), rectangle.top())); + break; + } + + case PageRotation::Rotate90: + { + unrotatedRectangle = rectangle.transposed(); + matrix.translate(rectangle.left(), rectangle.top()); + matrix.rotate(90); + matrix.translate(-rectangle.left(), -rectangle.top()); + matrix.translate(0, -unrotatedRectangle.height()); + break; + } + + case PageRotation::Rotate270: + { + unrotatedRectangle = rectangle.transposed(); + matrix.translate(rectangle.left(), rectangle.top()); + matrix.rotate(90); + matrix.translate(-rectangle.left(), -rectangle.top()); + matrix.translate(0, -unrotatedRectangle.height()); + matrix.translate(0.0, unrotatedRectangle.top() + unrotatedRectangle.bottom()); + matrix.scale(1.0, -1.0); + matrix.translate(unrotatedRectangle.left() + unrotatedRectangle.right(), 0.0); + matrix.scale(-1.0, 1.0); + break; + } + + default: + break; + } + + return PDFRenderer::createPagePointToDevicePointMatrix(page, unrotatedRectangle) * matrix; +} + void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect) { painter->fillRect(rect, Qt::lightGray); @@ -624,7 +674,7 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect) timer.start(); const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); - QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect); + QMatrix matrix = createPagePointToDevicePointMatrix(page, placedRect); compiledPage->draw(painter, page->getCropBox(), matrix, m_features); PDFTextLayoutGetter layoutGetter = m_textLayoutCompiler->getTextLayoutLazy(item.pageIndex); @@ -802,7 +852,7 @@ PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoin if (pagePoint) { const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); - QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect).inverted(); + QMatrix matrix = createPagePointToDevicePointMatrix(page, placedRect).inverted(); *pagePoint = matrix.map(point); } @@ -921,6 +971,18 @@ void PDFDrawWidgetProxy::performOperation(Operation operation) break; } + case RotateRight: + { + m_controller->setPageRotation(getPageRotationRotatedRight(m_controller->getPageRotation())); + break; + } + + case RotateLeft: + { + m_controller->setPageRotation(getPageRotationRotatedLeft(m_controller->getPageRotation())); + break; + } + default: { Q_ASSERT(false); diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.h b/PdfForQtLib/sources/pdfdrawspacecontroller.h index 16ca140..f54723c 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.h +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.h @@ -97,15 +97,14 @@ public: /// page and page rectangle, in which the page is contained. struct LayoutItem { - constexpr inline explicit LayoutItem() : blockIndex(-1), pageIndex(-1), pageRotation(PageRotation::None) { } - constexpr inline explicit LayoutItem(PDFInteger blockIndex, PDFInteger pageIndex, PageRotation rotation, const QRectF& pageRectMM) : - blockIndex(blockIndex), pageIndex(pageIndex), pageRotation(rotation), pageRectMM(pageRectMM) { } + constexpr inline explicit LayoutItem() : blockIndex(-1), pageIndex(-1) { } + constexpr inline explicit LayoutItem(PDFInteger blockIndex, PDFInteger pageIndex, const QRectF& pageRectMM) : + blockIndex(blockIndex), pageIndex(pageIndex), pageRectMM(pageRectMM) { } bool isValid() const { return pageIndex != -1; } PDFInteger blockIndex; PDFInteger pageIndex; - PageRotation pageRotation; QRectF pageRectMM; }; @@ -138,6 +137,12 @@ public: /// any page on the screen will fit this bounding box, regardless of mode (single/two columns, etc.). QSizeF getReferenceBoundingBox() const; + /// Returns page rotation + PageRotation getPageRotation() const { return m_pageRotation; } + + /// Sets page rotation + void setPageRotation(PageRotation pageRotation); + signals: void drawSpaceChanged(); void repaintNeeded(); @@ -169,6 +174,7 @@ private: BlockItems m_blockItems; PDFReal m_verticalSpacingMM; PDFReal m_horizontalSpacingMM; + PageRotation m_pageRotation; /// Font cache PDFFontCache m_fontCache; @@ -196,6 +202,12 @@ public: /// Updates the draw space area void update(); + /// Creates page point to device point matrix for the given rectangle. It creates transformation + /// from page's media box to the target rectangle. + /// \param page Page, for which we want to create matrix + /// \param rectangle Page rectangle, to which is page media box transformed + QMatrix createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const; + /// Draws the actually visible pages on the painter using the rectangle. /// Rectangle is space in the widget, which is used for painting the PDF. /// \param painter Painter to paint the PDF pages @@ -220,7 +232,9 @@ public: NavigateNextPage, NavigatePreviousPage, NavigateNextStep, - NavigatePreviousStep + NavigatePreviousStep, + RotateRight, + RotateLeft }; /// Performs the desired operation (for example navigation). @@ -329,13 +343,12 @@ signals: private: struct LayoutItem { - constexpr inline explicit LayoutItem() : pageIndex(-1), pageRotation(PageRotation::None) { } - constexpr inline explicit LayoutItem(PDFInteger pageIndex, PageRotation rotation, const QRect& pageRect) : - pageIndex(pageIndex), pageRotation(rotation), pageRect(pageRect) { } + constexpr inline explicit LayoutItem() : pageIndex(-1) { } + constexpr inline explicit LayoutItem(PDFInteger pageIndex, const QRect& pageRect) : + pageIndex(pageIndex), pageRect(pageRect) { } PDFInteger pageIndex; - PageRotation pageRotation; QRect pageRect; }; diff --git a/PdfForQtLib/sources/pdfpage.cpp b/PdfForQtLib/sources/pdfpage.cpp index 11c7d4b..ee9cefb 100644 --- a/PdfForQtLib/sources/pdfpage.cpp +++ b/PdfForQtLib/sources/pdfpage.cpp @@ -121,6 +121,11 @@ QRectF PDFPage::getRotatedMediaBox() const return getRotatedBox(getMediaBox(), getPageRotation()); } +QRectF PDFPage::getRotatedMediaBoxMM() const +{ + return getRotatedBox(getMediaBoxMM(), getPageRotation()); +} + QRectF PDFPage::getRotatedCropBox() const { return getRotatedBox(getCropBox(), getPageRotation()); diff --git a/PdfForQtLib/sources/pdfpage.h b/PdfForQtLib/sources/pdfpage.h index 6ff08da..6deab14 100644 --- a/PdfForQtLib/sources/pdfpage.h +++ b/PdfForQtLib/sources/pdfpage.h @@ -39,6 +39,40 @@ enum class PageRotation Rotate270 }; +constexpr PageRotation getPageRotationRotatedRight(PageRotation rotation) +{ + switch (rotation) + { + case PageRotation::None: + return PageRotation::Rotate90; + case PageRotation::Rotate90: + return PageRotation::Rotate180; + case PageRotation::Rotate180: + return PageRotation::Rotate270; + case PageRotation::Rotate270: + return PageRotation::None; + } + + return PageRotation::None; +} + +constexpr PageRotation getPageRotationRotatedLeft(PageRotation rotation) +{ + switch (rotation) + { + case PageRotation::None: + return PageRotation::Rotate270; + case PageRotation::Rotate90: + return PageRotation::None; + case PageRotation::Rotate180: + return PageRotation::Rotate90; + case PageRotation::Rotate270: + return PageRotation::Rotate180; + } + + return PageRotation::None; +} + /// This class represents attributes, which are inheritable. Also allows merging from /// parents. class PDFPageInheritableAttributes @@ -97,6 +131,7 @@ public: inline PDFObjectReference getPageReference() const { return m_pageReference; } QRectF getRotatedMediaBox() const; + QRectF getRotatedMediaBoxMM() const; QRectF getRotatedCropBox() const; static QRectF getRotatedBox(const QRectF& rect, PageRotation rotation); diff --git a/PdfForQtViewer/pdfforqtviewer.qrc b/PdfForQtViewer/pdfforqtviewer.qrc index 6674979..a1088ca 100644 --- a/PdfForQtViewer/pdfforqtviewer.qrc +++ b/PdfForQtViewer/pdfforqtviewer.qrc @@ -33,5 +33,7 @@ resources/find-previous.svg resources/select-text.svg resources/ui.svg + resources/rotate-left.svg + resources/rotate-right.svg diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 819b214..376a19d 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -107,6 +107,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : ui->actionSelectTextAll->setShortcut(QKeySequence::SelectAll); ui->actionDeselectText->setShortcut(QKeySequence::Deselect); ui->actionCopyText->setShortcut(QKeySequence::Copy); + ui->actionRotateRight->setShortcut(QKeySequence("Ctrl+Shift++")); + ui->actionRotateLeft->setShortcut(QKeySequence("Ctrl+Shift+-")); for (QAction* action : m_recentFileManager->getActions()) { @@ -1146,4 +1148,14 @@ void PDFViewerMainWindow::on_actionSend_by_E_Mail_triggered() } } +void PDFViewerMainWindow::on_actionRotateRight_triggered() +{ + m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::RotateRight); +} + +void PDFViewerMainWindow::on_actionRotateLeft_triggered() +{ + m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::RotateLeft); +} + } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index 89f9632..2b6c21e 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -92,6 +92,10 @@ private slots: void on_actionProperties_triggered(); void on_actionSend_by_E_Mail_triggered(); + void on_actionRotateRight_triggered(); + + void on_actionRotateLeft_triggered(); + private: void onActionOpenTriggered(); void onActionCloseTriggered(); diff --git a/PdfForQtViewer/pdfviewermainwindow.ui b/PdfForQtViewer/pdfviewermainwindow.ui index 64eee00..5fc6a17 100644 --- a/PdfForQtViewer/pdfviewermainwindow.ui +++ b/PdfForQtViewer/pdfviewermainwindow.ui @@ -68,6 +68,9 @@ + + + @@ -419,6 +422,24 @@ Invert Colors + + + + :/resources/rotate-right.svg:/resources/rotate-right.svg + + + Rotate Right + + + + + + :/resources/rotate-left.svg:/resources/rotate-left.svg + + + Rotate Left + + diff --git a/PdfForQtViewer/resources/rotate-left.svg b/PdfForQtViewer/resources/rotate-left.svg new file mode 100644 index 0000000..9d87433 --- /dev/null +++ b/PdfForQtViewer/resources/rotate-left.svg @@ -0,0 +1,84 @@ + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + diff --git a/PdfForQtViewer/resources/rotate-right.svg b/PdfForQtViewer/resources/rotate-right.svg new file mode 100644 index 0000000..c68e70c --- /dev/null +++ b/PdfForQtViewer/resources/rotate-right.svg @@ -0,0 +1,84 @@ + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + +