From d4087eae1a70274a7736dff35625ee9d29459cc5 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 27 Jan 2019 17:55:22 +0100 Subject: [PATCH] PDF draw widget (first part) --- PdfForQtLib/PdfForQtLib.pro | 8 +- .../sources/pdfdrawspacecontroller.cpp | 281 ++++++++++++++++++ PdfForQtLib/sources/pdfdrawspacecontroller.h | 156 +++++++++- PdfForQtLib/sources/pdfdrawwidget.cpp | 119 ++++++++ PdfForQtLib/sources/pdfdrawwidget.h | 79 +++++ PdfForQtLib/sources/pdfglobal.h | 27 +- PdfForQtViewer/main.cpp | 37 ++- PdfForQtViewer/pdfviewermainwindow.cpp | 176 +++++++++-- PdfForQtViewer/pdfviewermainwindow.h | 41 +++ PdfForQtViewer/pdfviewermainwindow.ui | 21 +- 10 files changed, 896 insertions(+), 49 deletions(-) create mode 100644 PdfForQtLib/sources/pdfdrawwidget.cpp create mode 100644 PdfForQtLib/sources/pdfdrawwidget.h diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 80e8095..0a381b5 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with PDFForQt. If not, see . -QT -= gui +QT += gui widgets TARGET = PdfForQtLib TEMPLATE = lib @@ -46,7 +46,8 @@ SOURCES += \ sources/pdfcatalog.cpp \ sources/pdfpage.cpp \ sources/pdfstreamfilters.cpp \ - sources/pdfdrawspacecontroller.cpp + sources/pdfdrawspacecontroller.cpp \ + sources/pdfdrawwidget.cpp HEADERS += \ sources/pdfobject.h \ @@ -63,7 +64,8 @@ HEADERS += \ sources/pdfnumbertreeloader.h \ sources/pdfpage.h \ sources/pdfstreamfilters.h \ - sources/pdfdrawspacecontroller.h + sources/pdfdrawspacecontroller.h \ + sources/pdfdrawwidget.h unix { target.path = /usr/lib diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp index c74b931..d28ae36 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp @@ -17,6 +17,9 @@ #include "pdfdrawspacecontroller.h" +#include "pdfdrawwidget.h" + +#include namespace pdf { @@ -31,6 +34,54 @@ PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) : } +void PDFDrawSpaceController::setDocument(const PDFDocument* document) +{ + if (document != m_document) + { + m_document = document; + recalculate(); + } +} + +void PDFDrawSpaceController::setPageLayout(PageLayout pageLayout) +{ + if (m_pageLayoutMode != pageLayout) + { + m_pageLayoutMode = pageLayout; + recalculate(); + } +} + +QRectF PDFDrawSpaceController::getBlockBoundingRectangle(size_t blockIndex) const +{ + if (blockIndex < m_blockItems.size()) + { + return m_blockItems[blockIndex].blockRectMM; + } + + return QRectF(); +} + +PDFDrawSpaceController::LayoutItems PDFDrawSpaceController::getLayoutItems(size_t blockIndex) const +{ + LayoutItems result; + + auto comparator = [](const LayoutItem& l, const LayoutItem& r) + { + return l.blockIndex < r.blockIndex; + }; + Q_ASSERT(std::is_sorted(m_layoutItems.cbegin(), m_layoutItems.cend(), comparator)); + + LayoutItem templateItem; + templateItem.blockIndex = blockIndex; + + auto range = std::equal_range(m_layoutItems.cbegin(), m_layoutItems.cend(), templateItem, comparator); + result.reserve(std::distance(range.first, range.second)); + std::copy(range.first, range.second, std::back_inserter(result)); + + return result; +} + void PDFDrawSpaceController::recalculate() { if (!m_document) @@ -55,6 +106,9 @@ void PDFDrawSpaceController::recalculate() pageRotation[layoutItem.pageIndex] = layoutItem.pageRotation; } + // Clear the old draw space + clear(false); + static constexpr size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); // Places the pages on the left/right sides. Pages can be nullptr, but not both of them. @@ -269,4 +323,231 @@ void PDFDrawSpaceController::clear(bool emitSignal) } } +PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) : + QObject(parent), + m_updateDisabled(false), + m_currentBlock(INVALID_BLOCK_INDEX), + m_pixelPerMM(PDF_DEFAULT_DPMM), + m_zoom(1.0), + m_pixelToDeviceSpaceUnit(0.0), + m_deviceSpaceUnitToPixel(0.0), + m_verticalOffset(0), + m_horizontalOffset(0), + m_controller(nullptr), + m_widget(nullptr), + m_horizontalScrollbar(nullptr), + m_verticalScrollbar(nullptr) +{ + m_controller = new PDFDrawSpaceController(this); + connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update); +} + +void PDFDrawWidgetProxy::setDocument(const PDFDocument* document) +{ + m_controller->setDocument(document); +} + +void PDFDrawWidgetProxy::init(PDFWidget* widget) +{ + m_widget = widget->getDrawWidget(); + m_horizontalScrollbar = widget->getHorizontalScrollbar(); + m_verticalScrollbar = widget->getVerticalScrollbar(); + + // We must update the draw space - widget has been set + update(); +} + +void PDFDrawWidgetProxy::update() +{ + if (m_updateDisabled) + { + return; + } + PDFBoolGuard guard(m_updateDisabled); + + Q_ASSERT(m_widget); + Q_ASSERT(m_horizontalScrollbar); + Q_ASSERT(m_verticalScrollbar); + + // First, we must calculate pixel per mm ratio to obtain DPMM (device pixel per mm), + // we also assume, that zoom is correctly set. + m_pixelPerMM = static_cast(m_widget->width()) / static_cast(m_widget->widthMM()); + + Q_ASSERT(m_zoom > 0.0); + Q_ASSERT(m_pixelPerMM > 0.0); + + m_deviceSpaceUnitToPixel = m_pixelPerMM * m_zoom; + m_pixelToDeviceSpaceUnit = 1.0 / m_deviceSpaceUnitToPixel; + + m_layout.clear(); + + // Switch to the first block, if we haven't selected any, otherwise fix active + // block item (select first block available). + if (m_controller->getBlockCount() > 0) + { + if (m_currentBlock == INVALID_BLOCK_INDEX) + { + m_currentBlock = 0; + } + else + { + m_currentBlock = qBound(0, m_currentBlock, m_controller->getBlockCount()); + } + } + else + { + m_currentBlock = INVALID_BLOCK_INDEX; + } + + // Then, create pixel size layout of the pages using the draw space controller + QRectF rectangle = m_controller->getBlockBoundingRectangle(m_currentBlock); + if (rectangle.isValid()) + { + // We must have a valid block + PDFDrawSpaceController::LayoutItems items = m_controller->getLayoutItems(m_currentBlock); + + 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.blockRect = fromDeviceSpace(rectangle).toRect(); + } + + QSize blockSize = m_layout.blockRect.size(); + QSize widgetSize = m_widget->size(); + + // Horizontal scrollbar + const int horizontalDifference = blockSize.width() - widgetSize.width(); + if (horizontalDifference > 0) + { + m_horizontalScrollbar->setVisible(true); + m_horizontalScrollbar->setMinimum(0); + m_horizontalScrollbar->setMaximum(horizontalDifference); + + m_horizontalOffset = qBound(0, m_horizontalOffset, horizontalDifference); + m_horizontalScrollbar->setValue(m_horizontalOffset); + } + else + { + // We do not need the horizontal scrollbar, because block can be draw onto widget entirely. + // We set the offset to the half of available empty space. + m_horizontalScrollbar->setVisible(false); + m_horizontalOffset = -horizontalDifference / 2; + } + + // Vertical scrollbar - has two meanings, in block mode, it switches between blocks, + // in continuous mode, it controls the vertical offset. + if (isBlockMode()) + { + size_t blockCount = m_controller->getBlockCount(); + if (blockCount > 0) + { + Q_ASSERT(m_currentBlock != INVALID_BLOCK_INDEX); + + m_verticalScrollbar->setVisible(blockCount > 1); + m_verticalScrollbar->setMinimum(0); + m_verticalScrollbar->setMaximum(static_cast(blockCount - 1)); + m_verticalScrollbar->setValue(static_cast(m_currentBlock)); + m_verticalScrollbar->setSingleStep(1); + m_verticalScrollbar->setPageStep(1); + } + else + { + Q_ASSERT(m_currentBlock == INVALID_BLOCK_INDEX); + m_verticalScrollbar->setVisible(false); + } + + // We must fix case, when we can display everything on the widget (we have + // enough space). Then we will center the page on the widget. + const int verticalDifference = blockSize.height() - widgetSize.height(); + if (verticalDifference < 0) + { + m_verticalOffset = -verticalDifference / 2; + } + } + else + { + const int verticalDifference = blockSize.height() - widgetSize.height(); + if (verticalDifference > 0) + { + m_verticalScrollbar->setVisible(true); + m_verticalScrollbar->setMinimum(0); + m_verticalScrollbar->setMaximum(verticalDifference); + + // We must also calculate single step/page step. Because pages can have different size, + // we use first page to compute page step. + if (!m_layout.items.empty()) + { + const LayoutItem& item = m_layout.items.front(); + + const int pageStep = qMax(item.pageRect.height(), 1); + const int singleStep = qMax(pageStep / 10, 1); + + m_verticalScrollbar->setPageStep(pageStep); + m_verticalScrollbar->setSingleStep(singleStep); + } + + m_verticalOffset = qBound(0, m_verticalOffset, verticalDifference); + m_verticalScrollbar->setValue(m_verticalOffset); + } + else + { + m_verticalScrollbar->setVisible(false); + m_verticalOffset = -verticalDifference / 2; + } + } + + emit drawSpaceChanged(); +} + +void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect) +{ + painter->fillRect(rect, Qt::lightGray); + + // Iterate trough pages and display them on the painter device + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + if (placedRect.intersects(rect)) + { + // Clear the page space by white color + painter->fillRect(placedRect, Qt::white); + } + } +} + +QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const +{ + Q_ASSERT(rect.isValid()); + + return QRectF(rect.left() * m_deviceSpaceUnitToPixel, + rect.top() * m_deviceSpaceUnitToPixel, + rect.width() * m_deviceSpaceUnitToPixel, + rect.height() * m_deviceSpaceUnitToPixel); +} + +bool PDFDrawWidgetProxy::isBlockMode() const +{ + switch (m_controller->getPageLayout()) + { + case PageLayout::OneColumn: + case PageLayout::TwoColumnLeft: + case PageLayout::TwoColumnRight: + return false; + + case PageLayout::SinglePage: + case PageLayout::TwoPagesLeft: + case PageLayout::TwoPagesRight: + return true; + } + + Q_ASSERT(false); + return false; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.h b/PdfForQtLib/sources/pdfdrawspacecontroller.h index 998d82d..7cfb6c7 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.h +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.h @@ -24,8 +24,13 @@ #include #include +class QPainter; +class QScrollBar; + namespace pdf { +class PDFWidget; +class PDFDrawWidget; /// This class controls draw space - page layout. Pages are divided into blocks /// each block can contain one or multiple pages. Units are in milimeters. @@ -37,15 +42,25 @@ class PDFDrawSpaceController : public QObject public: explicit PDFDrawSpaceController(QObject* parent); -signals: - void drawSpaceChanged(); + /// Sets the document and recalculates the draw space. Document can be nullptr, + /// in that case, draw space is cleared. + /// \param document Document + void setDocument(const PDFDocument* document); -private: - /// Recalculates the draw space. Preserves setted page rotation. - void recalculate(); + /// Sets the page layout. Page layout can be one of the PDF's page layouts. + /// \param pageLayout Page layout + void setPageLayout(PageLayout pageLayout); - /// Clears the draw space. Emits signal if desired. - void clear(bool emitSignal); + /// Returns the page layout + PageLayout getPageLayout() const { return m_pageLayoutMode; } + + /// Returns the block count + size_t getBlockCount() const { return m_blockItems.size(); } + + /// Return the bounding rectangle of the block. If block doesn't exist, + /// then invalid rectangle is returned (no exception is thrown). + /// \param blockIndex Index of the block + QRectF getBlockBoundingRectangle(size_t blockIndex) const; /// Represents layouted page. This structure contains index of the block, index of the /// page and page rectangle, in which the page is contained. @@ -63,6 +78,21 @@ private: using LayoutItems = std::vector; + /// Returns the layout items for desired block. If block doesn't exist, + /// then empty array is returned. + /// \param blockIndex Index of the block + LayoutItems getLayoutItems(size_t blockIndex) const; + +signals: + void drawSpaceChanged(); + +private: + /// Recalculates the draw space. Preserves setted page rotation. + void recalculate(); + + /// Clears the draw space. Emits signal if desired. + void clear(bool emitSignal); + /// Represents data for the single block. Contains block size in milimeters. struct LayoutBlock { @@ -83,6 +113,118 @@ private: PDFReal m_horizontalSpacingMM; }; +/// This is a proxy class to draw space controller using widget. We have two spaces, pixel space +/// (on the controlled widget) and device space (device is draw space controller). +class PDFDrawWidgetProxy : public QObject +{ + Q_OBJECT + +public: + explicit PDFDrawWidgetProxy(QObject* parent); + + /// Sets the document and updates the draw space. Document can be nullptr, + /// in that case, draw space is cleared. + /// \param document Document + void setDocument(const PDFDocument* document); + + void init(PDFWidget* widget); + + /// Updates the draw space area + void update(); + + /// 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 + /// \param rect Rectangle in which the content is painted + void draw(QPainter* painter, QRect rect); + +signals: + void drawSpaceChanged(); + +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) { } + + + PDFInteger pageIndex; + PageRotation pageRotation; + QRect pageRect; + }; + + struct Layout + { + inline void clear() + { + items.clear(); + blockRect = QRect(); + } + + std::vector items; + QRect blockRect; + }; + + static constexpr size_t INVALID_BLOCK_INDEX = std::numeric_limits::max(); + + // Minimal/maximal zoom is from 8% to 6400 %, according to the PDF 1.7 Reference, + // Appendix C. + + static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0; + static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0; + + /// Converts rectangle from device space to the pixel space + QRectF fromDeviceSpace(const QRectF& rect) const; + + /// Returns true, if we are in the block mode (multiple blocks with separate pages), + /// or continuous mode (single block with continuous list of separated pages). + bool isBlockMode() const; + + /// Flag, disables the update + bool m_updateDisabled; + + /// Current block (in the draw space controller) + size_t m_currentBlock; + + /// Number of pixels (fractional) per milimeter (unit is pixel/mm) of the screen, + /// so, size of the area in milimeters can be computed as pixelCount * m_pixelPerMM [mm]. + PDFReal m_pixelPerMM; + + /// Zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom, + /// and each 1 cm of widget area corresponds to 0.5 cm of the device space area. + PDFReal m_zoom; + + /// Converts pixel to device space units (mm) using zoom + PDFReal m_pixelToDeviceSpaceUnit; + + /// Converts device space units (mm) to real pixels using zoom + PDFReal m_deviceSpaceUnitToPixel; + + /// Actual vertical offset of the draw space area in the widget (so block will be drawn + /// with this vertical offset) + PDFInteger m_verticalOffset; + + /// Actual horizontal offset of the draw space area in the widget (so block will be drawn + /// with this horizontal offset) + PDFInteger m_horizontalOffset; + + /// Draw space controller + PDFDrawSpaceController* m_controller; + + /// Controlled draw widget (proxy is for this widget) + PDFDrawWidget* m_widget; + + /// Vertical scrollbar + QScrollBar* m_verticalScrollbar; + + /// Horizontal scrollbar + QScrollBar* m_horizontalScrollbar; + + /// Current page layout + Layout m_layout; +}; + } // namespace pdf #endif // PDFDRAWSPACECONTROLLER_H diff --git a/PdfForQtLib/sources/pdfdrawwidget.cpp b/PdfForQtLib/sources/pdfdrawwidget.cpp new file mode 100644 index 0000000..5e7b33a --- /dev/null +++ b/PdfForQtLib/sources/pdfdrawwidget.cpp @@ -0,0 +1,119 @@ +// Copyright (C) 2018 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + +#include "pdfdrawwidget.h" +#include "pdfdrawspacecontroller.h" + +#include +#include +#include + +namespace pdf +{ + +PDFWidget::PDFWidget(QWidget* parent) : + QWidget(parent), + m_drawWidget(nullptr), + m_horizontalScrollBar(nullptr), + m_verticalScrollBar(nullptr), + m_proxy(nullptr) +{ + m_drawWidget = new PDFDrawWidget(this, this); + m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this); + m_verticalScrollBar = new QScrollBar(Qt::Vertical, this); + + QGridLayout* layout = new QGridLayout(this); + layout->setSpacing(0); + layout->addWidget(m_drawWidget, 0, 0); + layout->addWidget(m_horizontalScrollBar, 1, 0); + layout->addWidget(m_verticalScrollBar, 0, 1); + layout->setMargin(0); + + setLayout(layout); + setFocusProxy(m_drawWidget); + + m_proxy = new PDFDrawWidgetProxy(this); + m_proxy->init(this); +} + +PDFWidget::~PDFWidget() +{ + +} + +void PDFWidget::setDocument(const PDFDocument* document) +{ + m_proxy->setDocument(document); +} + +PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) : + QWidget(parent), + m_widget(widget) +{ + setFocusPolicy(Qt::StrongFocus); +} + +PDFDrawWidget::~PDFDrawWidget() +{ + +} + +QSize PDFDrawWidget::minimumSizeHint() const +{ + return QSize(200, 200); +} + +void PDFDrawWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + m_widget->getDrawWidgetProxy()->draw(&painter, this->rect()); +} + +void PDFDrawWidget::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + + m_widget->getDrawWidgetProxy()->update(); +} + +void PDFDrawWidget::keyPressEvent(QKeyEvent* event) +{ + QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); + + // Vertical navigation + if (verticalScrollbar->isVisible()) + { + if (event->matches(QKeySequence::MoveToStartOfDocument)) + { + verticalScrollbar->setValue(0); + } + else if (event->matches(QKeySequence::MoveToEndOfDocument)) + { + verticalScrollbar->setValue(verticalScrollbar->maximum()); + } + else if (event->matches(QKeySequence::MoveToNextPage)) + { + verticalScrollbar->setValue(verticalScrollbar->value() + verticalScrollbar->pageStep()); + } + } + + event->accept(); +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfdrawwidget.h b/PdfForQtLib/sources/pdfdrawwidget.h new file mode 100644 index 0000000..ce6ab13 --- /dev/null +++ b/PdfForQtLib/sources/pdfdrawwidget.h @@ -0,0 +1,79 @@ +// Copyright (C) 2018 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + + +#ifndef PDFDRAWWIDGET_H +#define PDFDRAWWIDGET_H + +#include "pdfglobal.h" + +#include +#include + +namespace pdf +{ +class PDFDocument; +class PDFDrawWidget; +class PDFDrawWidgetProxy; + +class PDFFORQTLIBSHARED_EXPORT PDFWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PDFWidget(QWidget* parent); + virtual ~PDFWidget() override; + + /// Sets the document to be viewed in this widget. Document can be nullptr, + /// in that case, widget contents are cleared. + /// \param document Document + void setDocument(const PDFDocument* document); + + PDFDrawWidget* getDrawWidget() const { return m_drawWidget; } + QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; } + QScrollBar* getVerticalScrollbar() const { return m_verticalScrollBar; } + PDFDrawWidgetProxy* getDrawWidgetProxy() const { return m_proxy; } + +private: + PDFDrawWidget* m_drawWidget; + QScrollBar* m_horizontalScrollBar; + QScrollBar* m_verticalScrollBar; + PDFDrawWidgetProxy* m_proxy; +}; + +class PDFFORQTLIBSHARED_EXPORT PDFDrawWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PDFDrawWidget(PDFWidget* widget, QWidget* parent); + virtual ~PDFDrawWidget() override; + + virtual QSize minimumSizeHint() const override; + +protected: + virtual void paintEvent(QPaintEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; + virtual void keyPressEvent(QKeyEvent* event) override; + +private: + PDFWidget* m_widget; +}; + +} // namespace pdf + +#endif // PDFDRAWWIDGET_H diff --git a/PdfForQtLib/sources/pdfglobal.h b/PdfForQtLib/sources/pdfglobal.h index 0b6667d..8d5926b 100644 --- a/PdfForQtLib/sources/pdfglobal.h +++ b/PdfForQtLib/sources/pdfglobal.h @@ -105,14 +105,37 @@ struct PDFTranslationContext }; constexpr PDFReal PDF_POINT_TO_INCH = 1.0 / 72.0; -constexpr PDFReal PDF_INT_TO_MM = 25.4; -constexpr PDFReal PDF_POINT_TO_MM = PDF_POINT_TO_INCH * PDF_INT_TO_MM; +constexpr PDFReal PDF_INCH_TO_MM = 25.4; // [mm / inch] +constexpr PDFReal PDF_POINT_TO_MM = PDF_POINT_TO_INCH * PDF_INCH_TO_MM; + +/// This is default "DPI", but in milimeters, so the name is DPMM (device pixel per milimeter) +constexpr PDFReal PDF_DEFAULT_DPMM = 96.0 / PDF_INCH_TO_MM; constexpr PDFReal convertPDFPointToMM(PDFReal point) { return point * PDF_POINT_TO_MM; } +class PDFBoolGuard final +{ +public: + inline explicit PDFBoolGuard(bool& value) : + m_value(value), + m_oldValue(value) + { + m_value = true; + } + + inline ~PDFBoolGuard() + { + m_value = m_oldValue; + } + +private: + bool& m_value; + bool m_oldValue; +}; + } // namespace pdf #endif // PDFGLOBAL_H diff --git a/PdfForQtViewer/main.cpp b/PdfForQtViewer/main.cpp index 222d000..8dbf013 100644 --- a/PdfForQtViewer/main.cpp +++ b/PdfForQtViewer/main.cpp @@ -1,12 +1,41 @@ +// Copyright (C) 2019 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + #include "pdfviewermainwindow.h" #include +#include int main(int argc, char *argv[]) { - QApplication a(argc, argv); - pdfviewer::PDFViewerMainWindow w; - w.show(); + QApplication application(argc, argv); - return a.exec(); + QCoreApplication::setOrganizationName("MelkaJ"); + QCoreApplication::setApplicationName("PDF Viewer"); + QCoreApplication::setApplicationVersion("0.1"); + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::applicationName()); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("file", "The PDF file to open."); + parser.process(application); + + pdfviewer::PDFViewerMainWindow mainWindow; + mainWindow.show(); + + return application.exec(); } diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 509d35e..139743a 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -1,23 +1,60 @@ +// Copyright (C) 2019 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + #include "pdfviewermainwindow.h" #include "ui_pdfviewermainwindow.h" #include "pdfdocumentreader.h" #include "pdfvisitor.h" #include "pdfstreamfilters.h" +#include "pdfdrawwidget.h" +#include #include #include +#include +#include +#include +#include namespace pdfviewer { PDFViewerMainWindow::PDFViewerMainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::PDFViewerMainWindow) + ui(new Ui::PDFViewerMainWindow), + m_pdfWidget(nullptr) { ui->setupUi(this); + // Initialize shortcuts + ui->actionOpen->setShortcut(QKeySequence::Open); + ui->actionClose->setShortcut(QKeySequence::Close); + ui->actionQuit->setShortcut(QKeySequence::Quit); + connect(ui->actionOpen, &QAction::triggered, this, &PDFViewerMainWindow::onActionOpenTriggered); + connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered); + connect(ui->actionQuit, &QAction::triggered, this, &PDFViewerMainWindow::onActionQuitTriggered); + + m_pdfWidget = new pdf::PDFWidget(this); + setCentralWidget(m_pdfWidget); + setFocusProxy(m_pdfWidget); + + readSettings(); } PDFViewerMainWindow::~PDFViewerMainWindow() @@ -27,35 +64,120 @@ PDFViewerMainWindow::~PDFViewerMainWindow() void PDFViewerMainWindow::onActionOpenTriggered() { - QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), "K:/Programming/PDF/testpdf", tr("PDF document (*.pdf)")); + QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_directory, tr("PDF document (*.pdf)")); if (!fileName.isEmpty()) { - pdf::PDFDocumentReader reader; - pdf::PDFDocument document = reader.readFromFile(fileName); - pdf::PDFStatisticsCollector collector; - pdf::PDFApplyVisitor(document, &collector); - - if (reader.isSuccessfull()) - { - QMessageBox::information(this, tr("PDF Reader"), tr("Document '%1' was successfully loaded!").arg(fileName)); - } - else - { - QMessageBox::information(this, tr("PDF Reader"), tr("Document read error: %1").arg(reader.getErrorMessage())); - } - - const pdf::PDFCatalog* catalog = document.getCatalog(); - const pdf::PDFPage* page = catalog->getPage(0); - const pdf::PDFObject& contents = page->getContents(); - - if (contents.isStream()) - { - const pdf::PDFStream* stream = contents.getStream(); - const QByteArray* compressed = stream->getContent(); - pdf::PDFFlateDecodeFilter fd; - QByteArray uncompressed = fd.apply(*compressed, &document, pdf::PDFObject()); - } + openDocument(fileName); } } +void PDFViewerMainWindow::onActionCloseTriggered() +{ + closeDocument(); +} + +void PDFViewerMainWindow::onActionQuitTriggered() +{ + close(); +} + +void PDFViewerMainWindow::readSettings() +{ + QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName()); + + QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray(); + if (geometry.isEmpty()) + { + QRect availableGeometry = QApplication::desktop()->availableGeometry(this); + QRect windowRect(0, 0, availableGeometry.width() / 2, availableGeometry.height() / 2); + windowRect = windowRect.translated(availableGeometry.center() - windowRect.center()); + setGeometry(windowRect); + } + else + { + restoreGeometry(geometry); + } + + QByteArray state = settings.value("windowState", QByteArray()).toByteArray(); + if (!state.isEmpty()) + { + restoreState(state); + } + + m_directory = settings.value("defaultDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString(); +} + +void PDFViewerMainWindow::writeSettings() +{ + QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName()); + settings.setValue("geometry", saveGeometry()); + settings.setValue("windowState", saveState()); + settings.setValue("defaultDirectory", m_directory); +} + +void PDFViewerMainWindow::updateTitle() +{ + if (m_pdfDocument) + { + QString title = m_pdfDocument->getInfo()->title; + if (title.isEmpty()) + { + title = m_currentFile; + } + setWindowTitle(tr("%1 - PDF Viewer").arg(m_currentFile)); + } + else + { + setWindowTitle(tr("PDF Viewer")); + } +} + +void PDFViewerMainWindow::openDocument(const QString& fileName) +{ + // First close old document + closeDocument(); + + // Try to open a new document + QApplication::setOverrideCursor(Qt::WaitCursor); + pdf::PDFDocumentReader reader; + pdf::PDFDocument document = reader.readFromFile(fileName); + QApplication::restoreOverrideCursor(); + + if (reader.isSuccessfull()) + { + // Mark current directory as this + QFileInfo fileInfo(fileName); + m_directory = fileInfo.dir().absolutePath(); + m_currentFile = fileInfo.fileName(); + + m_pdfDocument.reset(new pdf::PDFDocument(std::move(document))); + setDocument(m_pdfDocument.data()); + + statusBar()->showMessage(tr("Document '%1' was successfully loaded!").arg(fileName), 4000); + } + else + { + QMessageBox::critical(this, tr("PDF Viewer"), tr("Document read error: %1").arg(reader.getErrorMessage())); + } +} + +void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document) +{ + m_pdfWidget->setDocument(document); + updateTitle(); +} + +void PDFViewerMainWindow::closeDocument() +{ + setDocument(nullptr); + m_pdfDocument.reset(); +} + +void PDFViewerMainWindow::closeEvent(QCloseEvent* event) +{ + writeSettings(); + closeDocument(); + event->accept(); +} + } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfviewermainwindow.h b/PdfForQtViewer/pdfviewermainwindow.h index 62e19a9..a542b6e 100644 --- a/PdfForQtViewer/pdfviewermainwindow.h +++ b/PdfForQtViewer/pdfviewermainwindow.h @@ -1,13 +1,37 @@ +// Copyright (C) 2019 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// PdfForQt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDFForQt. If not, see . + #ifndef PDFVIEWERMAINWINDOW_H #define PDFVIEWERMAINWINDOW_H #include +#include namespace Ui { class PDFViewerMainWindow; } +namespace pdf +{ +class PDFWidget; +class PDFDocument; +} + namespace pdfviewer { @@ -19,10 +43,27 @@ public: explicit PDFViewerMainWindow(QWidget *parent = nullptr); virtual ~PDFViewerMainWindow() override; + virtual void closeEvent(QCloseEvent* event) override; + private: void onActionOpenTriggered(); + void onActionCloseTriggered(); + void onActionQuitTriggered(); + + void readSettings(); + void writeSettings(); + + void updateTitle(); + + void openDocument(const QString& fileName); + void setDocument(const pdf::PDFDocument* document); + void closeDocument(); Ui::PDFViewerMainWindow* ui; + pdf::PDFWidget* m_pdfWidget; + QSharedPointer m_pdfDocument; + QString m_directory; + QString m_currentFile; }; } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfviewermainwindow.ui b/PdfForQtViewer/pdfviewermainwindow.ui index 298d18e..e45f76d 100644 --- a/PdfForQtViewer/pdfviewermainwindow.ui +++ b/PdfForQtViewer/pdfviewermainwindow.ui @@ -6,12 +6,12 @@ 0 0 - 400 - 300 + 431 + 333 - PDFViewerMainWindow + PDF Viewer @@ -19,7 +19,7 @@ 0 0 - 400 + 431 21 @@ -28,6 +28,8 @@ File + + @@ -44,8 +46,15 @@ Open - - Ctrl+O + + + + Close + + + + + Quit