Draw space controller

This commit is contained in:
Jakub Melka 2019-01-20 17:55:06 +01:00
parent a9292a4c02
commit 7631265ba4
11 changed files with 458 additions and 17 deletions

View File

@ -45,7 +45,8 @@ SOURCES += \
sources/pdfencoding.cpp \
sources/pdfcatalog.cpp \
sources/pdfpage.cpp \
sources/pdfstreamfilters.cpp
sources/pdfstreamfilters.cpp \
sources/pdfdrawspacecontroller.cpp
HEADERS += \
sources/pdfobject.h \
@ -61,7 +62,8 @@ HEADERS += \
sources/pdfcatalog.h \
sources/pdfnumbertreeloader.h \
sources/pdfpage.h \
sources/pdfstreamfilters.h
sources/pdfstreamfilters.h \
sources/pdfdrawspacecontroller.h
unix {
target.path = /usr/lib

View File

@ -196,6 +196,12 @@ public:
/// Returns viewer preferences of the application
const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; }
/// Returns the page count
size_t getPageCount() const { return m_pages.size(); }
/// Returns the page
const PDFPage* getPage(size_t index) const { return &m_pages.at(index); }
/// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs,
/// then exception is thrown.
static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document);

View File

@ -173,6 +173,9 @@ public:
/// is returned (no exception is thrown).
const PDFObject& getObject(const PDFObject& object) const;
/// Returns the document catalog
const PDFCatalog* getCatalog() const { return &m_catalog; }
private:
friend class PDFDocumentReader;

View File

@ -0,0 +1,272 @@
// 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 "pdfdrawspacecontroller.h"
namespace pdf
{
PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) :
QObject(parent),
m_document(nullptr),
m_pageLayoutMode(PageLayout::SinglePage),
m_verticalSpacingMM(5.0),
m_horizontalSpacingMM(1.0)
{
}
void PDFDrawSpaceController::recalculate()
{
if (!m_document)
{
clear(true);
return;
}
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> 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;
}
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.
// Updates bounding rectangle.
auto placePagesLeftRight = [this, catalog, &pageRotation](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();
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);
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();
PDFReal xPos = m_horizontalSpacingMM * 0.5;
QRectF rect(xPos, yPos, pageSize.width(), pageSize.height());
m_layoutItems.emplace_back(blockIndex, rightIndex, pageRotation[rightIndex], rect);
yPosAdvance = qMax(yPosAdvance, pageSize.height());
boundingRect = boundingRect.united(rect);
}
if (yPosAdvance > 0.0)
{
yPos += yPosAdvance + m_verticalSpacingMM;
}
};
// Generates block with pages using page indices. If generateBlocks is true, then
// for each pair of pages, single block is generated, otherwise block containing all
// pages is generated.
auto placePagesLeftRightByIndices = [this, &placePagesLeftRight](const std::vector<size_t>& indices, bool generateBlocks)
{
Q_ASSERT(indices.size() % 2 == 0);
PDFReal yPos = 0.0;
PDFInteger blockIndex = 0;
QRectF boundingRectangle;
size_t count = indices.size() / 2;
for (size_t i = 0; i < count; ++i)
{
const size_t leftPageIndex = indices[2 * i];
const size_t rightPageIndex = indices[2 * i + 1];
placePagesLeftRight(blockIndex, leftPageIndex, rightPageIndex, yPos, boundingRectangle);
if (generateBlocks)
{
m_blockItems.emplace_back(boundingRectangle);
// Clear the old data
yPos = 0.0;
++blockIndex;
boundingRectangle = QRectF();
}
}
if (!generateBlocks)
{
// Generate single block for all layed out pages
m_blockItems.emplace_back(boundingRectangle);
}
};
switch (m_pageLayoutMode)
{
case PageLayout::SinglePage:
{
// Each block contains single page
m_layoutItems.reserve(pageCount);
m_blockItems.reserve(pageCount);
// Pages can have different size, so we center them around the center.
// Block size will equal to the page size.
for (size_t i = 0; i < pageCount; ++i)
{
QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getMediaBoxMM(), pageRotation[i]).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_blockItems.emplace_back(rect);
}
break;
}
case PageLayout::OneColumn:
{
// Single block, one column
m_layoutItems.reserve(pageCount);
m_blockItems.reserve(1);
PDFReal yPos = 0.0;
QRectF boundingRectangle;
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();
QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height());
m_layoutItems.emplace_back(0, i, pageRotation[i], rect);
yPos += pageSize.height() + m_verticalSpacingMM;
boundingRectangle = boundingRectangle.united(rect);
}
// Insert the single block with union of bounding rectangles
m_blockItems.emplace_back(boundingRectangle);
break;
}
case PageLayout::TwoColumnLeft:
{
// Pages with number 1, 3, 5, ... are on the left, 2, 4, 6 are on the right.
// Page indices are numbered from 0, so pages 0, 2, 4 will be on the left,
// 1, 3, 5 will be on the right.
// For purposes or paging, "left" pages will be on the left side of y axis (negative x axis),
// the "right" pages will be on the right side of y axis (positive x axis).
m_layoutItems.reserve(pageCount);
m_blockItems.reserve(1);
std::vector<size_t> pageIndices(pageCount, INVALID_PAGE_INDEX);
std::iota(pageIndices.begin(), pageIndices.end(), static_cast<size_t>(0));
if (pageIndices.size() % 2 == 1)
{
pageIndices.push_back(INVALID_PAGE_INDEX);
}
placePagesLeftRightByIndices(pageIndices, false);
break;
}
case PageLayout::TwoColumnRight:
{
// Similar to previous case, but page sequence start on the right.
m_layoutItems.reserve(pageCount);
m_blockItems.reserve(1);
std::vector<size_t> pageIndices(pageCount + 1, INVALID_PAGE_INDEX);
std::iota(std::next(pageIndices.begin()), pageIndices.end(), static_cast<size_t>(0));
if (pageIndices.size() % 2 == 1)
{
pageIndices.push_back(INVALID_PAGE_INDEX);
}
placePagesLeftRightByIndices(pageIndices, false);
break;
}
case PageLayout::TwoPagesLeft:
{
m_layoutItems.reserve(pageCount);
m_blockItems.reserve((pageCount / 2) + (pageCount % 2));
std::vector<size_t> pageIndices(pageCount, INVALID_PAGE_INDEX);
std::iota(pageIndices.begin(), pageIndices.end(), static_cast<size_t>(0));
if (pageIndices.size() % 2 == 1)
{
pageIndices.push_back(INVALID_PAGE_INDEX);
}
placePagesLeftRightByIndices(pageIndices, true);
break;
}
case PageLayout::TwoPagesRight:
{
m_layoutItems.reserve(pageCount);
m_blockItems.reserve((pageCount / 2) + (pageCount % 2));
std::vector<size_t> pageIndices(pageCount + 1, INVALID_PAGE_INDEX);
std::iota(std::next(pageIndices.begin()), pageIndices.end(), static_cast<size_t>(0));
if (pageIndices.size() % 2 == 1)
{
pageIndices.push_back(INVALID_PAGE_INDEX);
}
placePagesLeftRightByIndices(pageIndices, true);
break;
}
default:
{
Q_ASSERT(false);
break;
}
}
emit drawSpaceChanged();
}
void PDFDrawSpaceController::clear(bool emitSignal)
{
m_layoutItems.clear();
m_blockItems.clear();
if (emitSignal)
{
emit drawSpaceChanged();
}
}
} // namespace pdf

View File

@ -0,0 +1,88 @@
// 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 PDFDRAWSPACECONTROLLER_H
#define PDFDRAWSPACECONTROLLER_H
#include "pdfdocument.h"
#include <QRectF>
#include <QObject>
#include <QMarginsF>
namespace pdf
{
/// This class controls draw space - page layout. Pages are divided into blocks
/// each block can contain one or multiple pages. Units are in milimeters.
/// Pages are layouted in zoom-independent mode.
class PDFDrawSpaceController : public QObject
{
Q_OBJECT
public:
explicit PDFDrawSpaceController(QObject* parent);
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 layouted page. This structure contains index of the block, index of the
/// 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) { }
PDFInteger blockIndex;
PDFInteger pageIndex;
PageRotation pageRotation;
QRectF pageRectMM;
};
using LayoutItems = std::vector<LayoutItem>;
/// Represents data for the single block. Contains block size in milimeters.
struct LayoutBlock
{
constexpr inline explicit LayoutBlock() = default;
constexpr inline explicit LayoutBlock(const QRectF& blockRectMM) : blockRectMM(blockRectMM) { }
QRectF blockRectMM;
};
using BlockItems = std::vector<LayoutBlock>;
const PDFDocument* m_document;
PageLayout m_pageLayoutMode;
LayoutItems m_layoutItems;
BlockItems m_blockItems;
PDFReal m_verticalSpacingMM;
PDFReal m_horizontalSpacingMM;
};
} // namespace pdf
#endif // PDFDRAWSPACECONTROLLER_H

View File

@ -104,6 +104,15 @@ struct PDFTranslationContext
Q_DECLARE_TR_FUNCTIONS(pdf::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 convertPDFPointToMM(PDFReal point)
{
return point * PDF_POINT_TO_MM;
}
} // namespace pdf
#endif // PDFGLOBAL_H

View File

@ -108,6 +108,31 @@ std::vector<PDFPage> PDFPage::parse(const PDFDocument* document, const PDFObject
return result;
}
QRectF PDFPage::getRectMM(const QRectF& rect) const
{
return QRectF(convertPDFPointToMM(rect.left()),
convertPDFPointToMM(rect.top()),
convertPDFPointToMM(rect.width()),
convertPDFPointToMM(rect.height()));
}
QRectF PDFPage::getRotatedBox(const QRectF& rect, PageRotation rotation)
{
switch (rotation)
{
case PageRotation::None:
case PageRotation::Rotate180:
// Preserve rotation
break;
case PageRotation::Rotate90:
case PageRotation::Rotate270:
return rect.transposed();
}
return rect;
}
void PDFPage::parseImpl(std::vector<PDFPage>& pages,
std::set<PDFObjectReference>& visitedReferences,
const PDFPageInheritableAttributes& templateAttributes,
@ -165,7 +190,7 @@ void PDFPage::parseImpl(std::vector<PDFPage>& pages,
page.m_mediaBox = currentInheritableAttributes.getMediaBox();
page.m_cropBox = currentInheritableAttributes.getCropBox();
page.m_resources = currentInheritableAttributes.getResources();
page.m_resources = document->getObject(currentInheritableAttributes.getResources());
page.m_pageRotation = currentInheritableAttributes.getPageRotation();
if (!page.m_cropBox.isValid())

View File

@ -76,14 +76,24 @@ public:
/// \param root Root object of page tree
static std::vector<PDFPage> parse(const PDFDocument* document, const PDFObject& root);
const QRectF& getMediaBox() const { return m_mediaBox; }
const QRectF& getCropBox() const { return m_cropBox; }
const QRectF& getBleedBox() const { return m_bleedBox; }
const QRectF& getTrimBox() const { return m_trimBox; }
const QRectF& getArtBox() const { return m_artBox; }
PageRotation getPageRotation() const { return m_pageRotation; }
const PDFObject& getResources() const { return m_resources; }
const PDFObject& getContents() const { return m_contents; }
inline const QRectF& getMediaBox() const { return m_mediaBox; }
inline const QRectF& getCropBox() const { return m_cropBox; }
inline const QRectF& getBleedBox() const { return m_bleedBox; }
inline const QRectF& getTrimBox() const { return m_trimBox; }
inline const QRectF& getArtBox() const { return m_artBox; }
inline PageRotation getPageRotation() const { return m_pageRotation; }
inline const PDFObject& getResources() const { return m_resources; }
inline const PDFObject& getContents() const { return m_contents; }
QRectF getRectMM(const QRectF& rect) const;
inline QRectF getMediaBoxMM() const { return getRectMM(m_mediaBox); }
inline QRectF getCropBoxMM() const { return getRectMM(m_cropBox); }
inline QRectF getBleedBoxMM() const { return getRectMM(m_bleedBox); }
inline QRectF getTrimBoxMM() const { return getRectMM(m_trimBox); }
inline QRectF getArtBoxMM() const { return getRectMM(m_artBox); }
static QRectF getRotatedBox(const QRectF& rect, PageRotation rotation);
private:
/// Parses the page tree (implementation). If error occurs, then exception is thrown.

View File

@ -26,7 +26,7 @@ namespace pdf
{
class PDFDocument;
class PDFStreamFilter
class PDFFORQTLIBSHARED_EXPORT PDFStreamFilter
{
public:
explicit PDFStreamFilter() = default;
@ -35,7 +35,7 @@ public:
virtual QByteArray apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const = 0;
};
class PDFAsciiHexDecodeFilter : public PDFStreamFilter
class PDFFORQTLIBSHARED_EXPORT PDFAsciiHexDecodeFilter : public PDFStreamFilter
{
public:
explicit PDFAsciiHexDecodeFilter() = default;
@ -44,7 +44,7 @@ public:
virtual QByteArray apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const override;
};
class PDFAscii85DecodeFilter : public PDFStreamFilter
class PDFFORQTLIBSHARED_EXPORT PDFAscii85DecodeFilter : public PDFStreamFilter
{
public:
explicit PDFAscii85DecodeFilter() = default;
@ -53,7 +53,7 @@ public:
virtual QByteArray apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const override;
};
class PDFLzwDecodeFilter : public PDFStreamFilter
class PDFFORQTLIBSHARED_EXPORT PDFLzwDecodeFilter : public PDFStreamFilter
{
public:
explicit PDFLzwDecodeFilter() = default;
@ -62,7 +62,7 @@ public:
virtual QByteArray apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const override;
};
class PDFFlateDecodeFilter : public PDFStreamFilter
class PDFFORQTLIBSHARED_EXPORT PDFFlateDecodeFilter : public PDFStreamFilter
{
public:
explicit PDFFlateDecodeFilter() = default;
@ -71,7 +71,7 @@ public:
virtual QByteArray apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const override;
};
class PDFRunLengthDecodeFilter : public PDFStreamFilter
class PDFFORQTLIBSHARED_EXPORT PDFRunLengthDecodeFilter : public PDFStreamFilter
{
public:
explicit PDFRunLengthDecodeFilter() = default;

View File

@ -3,6 +3,7 @@
#include "pdfdocumentreader.h"
#include "pdfvisitor.h"
#include "pdfstreamfilters.h"
#include <QFileDialog>
#include <QMessageBox>
@ -42,6 +43,18 @@ void PDFViewerMainWindow::onActionOpenTriggered()
{
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());
}
}
}

View File

@ -22,6 +22,7 @@
#include "pdfparser.h"
#include "pdfconstants.h"
#include "pdfflatmap.h"
#include "pdfstreamfilters.h"
#include <regex>
@ -44,6 +45,7 @@ private slots:
void test_invalid_input();
void test_header_regexp();
void test_flat_map();
void test_lzw_filter();
private:
void scanWholeStream(const char* stream);
@ -295,6 +297,17 @@ void LexicalAnalyzerTest::test_flat_map()
}
}
void LexicalAnalyzerTest::test_lzw_filter()
{
// This example is from PDF 1.7 Reference
QByteArray byteArray = QByteArray::fromHex("800B6050220C0C8501");
pdf::PDFLzwDecodeFilter filter;
QByteArray decoded = filter.apply(byteArray, nullptr, pdf::PDFObject());
QByteArray valid = "-----A---B";
QCOMPARE(decoded, valid);
}
void LexicalAnalyzerTest::scanWholeStream(const char* stream)
{
pdf::PDFLexicalAnalyzer analyzer(stream, stream + strlen(stream));