mirror of https://github.com/JakubMelka/PDF4QT.git
PDF draw widget (first part)
This commit is contained in:
parent
7631265ba4
commit
d4087eae1a
|
@ -15,7 +15,7 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
|
||||
#include "pdfdrawspacecontroller.h"
|
||||
#include "pdfdrawwidget.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
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<size_t>::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<PDFReal>(m_widget->width()) / static_cast<PDFReal>(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<PDFInteger>(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<PDFInteger>(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<int>(blockCount - 1));
|
||||
m_verticalScrollbar->setValue(static_cast<int>(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<PDFInteger>(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
|
||||
|
|
|
@ -24,8 +24,13 @@
|
|||
#include <QObject>
|
||||
#include <QMarginsF>
|
||||
|
||||
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<LayoutItem>;
|
||||
|
||||
/// 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<LayoutItem> items;
|
||||
QRect blockRect;
|
||||
};
|
||||
|
||||
static constexpr size_t INVALID_BLOCK_INDEX = std::numeric_limits<size_t>::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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "pdfdrawwidget.h"
|
||||
#include "pdfdrawspacecontroller.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QGridLayout>
|
||||
#include <QKeyEvent>
|
||||
|
||||
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
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef PDFDRAWWIDGET_H
|
||||
#define PDFDRAWWIDGET_H
|
||||
|
||||
#include "pdfglobal.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QScrollBar>
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "pdfviewermainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "pdfviewermainwindow.h"
|
||||
#include "ui_pdfviewermainwindow.h"
|
||||
|
||||
#include "pdfdocumentreader.h"
|
||||
#include "pdfvisitor.h"
|
||||
#include "pdfstreamfilters.h"
|
||||
#include "pdfdrawwidget.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QCloseEvent>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QStandardPaths>
|
||||
|
||||
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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef PDFVIEWERMAINWINDOW_H
|
||||
#define PDFVIEWERMAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSharedPointer>
|
||||
|
||||
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<pdf::PDFDocument> m_pdfDocument;
|
||||
QString m_directory;
|
||||
QString m_currentFile;
|
||||
};
|
||||
|
||||
} // namespace pdfviewer
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
<width>431</width>
|
||||
<height>333</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>PDFViewerMainWindow</string>
|
||||
<string>PDF Viewer</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget"/>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
|
@ -19,7 +19,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<width>431</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -28,6 +28,8 @@
|
|||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionClose"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
</widget>
|
||||
|
@ -44,8 +46,15 @@
|
|||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+O</string>
|
||||
</action>
|
||||
<action name="actionClose">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionQuit">
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
|
|
Loading…
Reference in New Issue