Color spaces, beginning of painting

This commit is contained in:
Jakub Melka 2019-02-09 18:40:56 +01:00
parent 0a28869c94
commit 9264ea70c1
13 changed files with 703 additions and 2 deletions

View File

@ -47,7 +47,9 @@ SOURCES += \
sources/pdfpage.cpp \
sources/pdfstreamfilters.cpp \
sources/pdfdrawspacecontroller.cpp \
sources/pdfdrawwidget.cpp
sources/pdfdrawwidget.cpp \
sources/pdfcolorspaces.cpp \
sources/pdfrenderer.cpp
HEADERS += \
sources/pdfobject.h \
@ -65,7 +67,10 @@ HEADERS += \
sources/pdfpage.h \
sources/pdfstreamfilters.h \
sources/pdfdrawspacecontroller.h \
sources/pdfdrawwidget.h
sources/pdfdrawwidget.h \
sources/pdfflatarray.h \
sources/pdfcolorspaces.h \
sources/pdfrenderer.h
unix {
target.path = /usr/lib

View File

@ -0,0 +1,76 @@
// 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 "pdfcolorspaces.h"
namespace pdf
{
QColor PDFDeviceGrayColorSpace::getColor(const PDFColor& color) const
{
Q_ASSERT(color.size() == getColorComponentCount());
PDFColorComponent component = clip01(color[0]);
QColor result(QColor::Rgb);
result.setRgbF(component, component, component, 1.0);
return result;
}
size_t PDFDeviceGrayColorSpace::getColorComponentCount() const
{
return 1;
}
QColor PDFDeviceRGBColorSpace::getColor(const PDFColor& color) const
{
Q_ASSERT(color.size() == getColorComponentCount());
PDFColorComponent r = clip01(color[0]);
PDFColorComponent g = clip01(color[1]);
PDFColorComponent b = clip01(color[2]);
QColor result(QColor::Rgb);
result.setRgbF(r, g, b, 1.0);
return result;
}
size_t PDFDeviceRGBColorSpace::getColorComponentCount() const
{
return 3;
}
QColor PDFDeviceCMYKColorSpace::getColor(const PDFColor& color) const
{
Q_ASSERT(color.size() == getColorComponentCount());
PDFColorComponent c = clip01(color[0]);
PDFColorComponent m = clip01(color[1]);
PDFColorComponent y = clip01(color[2]);
PDFColorComponent k = clip01(color[3]);
QColor result(QColor::Cmyk);
result.setCmykF(c, m, y, k, 1.0);
return result;
}
size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const
{
return 4;
}
} // namespace pdf

View File

@ -0,0 +1,78 @@
// 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 PDFCOLORSPACES_H
#define PDFCOLORSPACES_H
#include "pdfflatarray.h"
#include <QColor>
namespace pdf
{
using PDFColorComponent = float;
using PDFColor = PDFFlatArray<PDFColorComponent, 4>;
/// Represents PDF's color space
class PDFAbstractColorSpace
{
public:
explicit PDFAbstractColorSpace() = default;
virtual ~PDFAbstractColorSpace() = default;
virtual QColor getColor(const PDFColor& color) const = 0;
virtual size_t getColorComponentCount() const = 0;
protected:
/// Clips the color component to range [0, 1]
static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound<PDFColorComponent>(0.0, component, 1.0); }
};
class PDFDeviceGrayColorSpace : public PDFAbstractColorSpace
{
public:
explicit PDFDeviceGrayColorSpace() = default;
virtual ~PDFDeviceGrayColorSpace() = default;
virtual QColor getColor(const PDFColor& color) const override;
virtual size_t getColorComponentCount() const override;
};
class PDFDeviceRGBColorSpace : public PDFAbstractColorSpace
{
public:
explicit PDFDeviceRGBColorSpace() = default;
virtual ~PDFDeviceRGBColorSpace() = default;
virtual QColor getColor(const PDFColor& color) const override;
virtual size_t getColorComponentCount() const override;
};
class PDFDeviceCMYKColorSpace : public PDFAbstractColorSpace
{
public:
explicit PDFDeviceCMYKColorSpace() = default;
virtual ~PDFDeviceCMYKColorSpace() = default;
virtual QColor getColor(const PDFColor& color) const override;
virtual size_t getColorComponentCount() const override;
};
} // namespace pdf
#endif // PDFCOLORSPACES_H

View File

@ -19,6 +19,8 @@
#include "pdfdocument.h"
#include "pdfparser.h"
#include "pdfencoding.h"
#include "pdfstreamfilters.h"
#include "pdfconstants.h"
namespace pdf
{
@ -38,6 +40,96 @@ static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE = "True";
static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE = "False";
static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN = "Unknown";
QByteArray PDFDocument::getDecodedStream(const PDFStream* stream) const
{
const PDFDictionary* dictionary = stream->getDictionary();
// Retrieve filters
PDFObject filters;
if (dictionary->hasKey(PDF_STREAM_DICT_FILTER))
{
filters = getObject(dictionary->get(PDF_STREAM_DICT_FILTER));
}
else if (dictionary->hasKey(PDF_STREAM_DICT_FILE_FILTER))
{
filters = getObject(dictionary->get(PDF_STREAM_DICT_FILE_FILTER));
}
// Retrieve filter parameters
PDFObject filterParameters;
if (dictionary->hasKey(PDF_STREAM_DICT_DECODE_PARMS))
{
filterParameters = getObject(dictionary->get(PDF_STREAM_DICT_DECODE_PARMS));
}
else if (dictionary->hasKey(PDF_STREAM_DICT_FDECODE_PARMS))
{
filterParameters = getObject(dictionary->get(PDF_STREAM_DICT_FDECODE_PARMS));
}
std::vector<const PDFStreamFilter*> filterObjects;
std::vector<PDFObject> filterParameterObjects;
if (filters.isName())
{
filterObjects.push_back(PDFStreamFilterStorage::getFilter(filters.getString()));
}
else if (filters.isArray())
{
const PDFArray* filterArray = filters.getArray();
const size_t filterCount = filterArray->getCount();
for (size_t i = 0; i < filterCount; ++i)
{
const PDFObject& object = getObject(filterArray->getItem(i));
if (object.isName())
{
filterObjects.push_back(PDFStreamFilterStorage::getFilter(object.getString()));
}
else
{
return QByteArray();
}
}
}
else if (!filters.isNull())
{
return QByteArray();
}
if (filterParameters.isArray())
{
const PDFArray* filterParameterArray = filterParameters.getArray();
const size_t filterParameterCount = filterParameterArray->getCount();
for (size_t i = 0; i < filterParameterCount; ++i)
{
const PDFObject& object = getObject(filterParameterArray->getItem(i));
filterParameterObjects.push_back(object);
}
}
else
{
filterParameterObjects.push_back(filterParameters);
}
filterParameterObjects.resize(filterObjects.size());
std::reverse(filterObjects.begin(), filterObjects.end());
std::reverse(filterParameterObjects.begin(), filterParameterObjects.end());
QByteArray result = *stream->getContent();
for (size_t i = 0, count = filterObjects.size(); i < count; ++i)
{
const PDFStreamFilter* streamFilter = filterObjects[i];
const PDFObject& streamFilterParameters = filterParameterObjects[i];
if (streamFilter)
{
result = streamFilter->apply(result, this, streamFilterParameters);
}
}
return result;
}
void PDFDocument::init()
{
initInfo();

View File

@ -176,6 +176,11 @@ public:
/// Returns the document catalog
const PDFCatalog* getCatalog() const { return &m_catalog; }
/// Returns the decoded stream. If stream data cannot be decoded,
/// then empty byte array is returned.
/// \param stream Stream to be decoded
QByteArray getDecodedStream(const PDFStream* stream) const;
private:
friend class PDFDocumentReader;

View File

@ -18,6 +18,7 @@
#include "pdfdrawspacecontroller.h"
#include "pdfdrawwidget.h"
#include "pdfrenderer.h"
#include <QPainter>
@ -536,6 +537,9 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
font.setPixelSize(placedRect.height() * 0.75);
painter->setFont(font);
painter->drawText(placedRect, Qt::AlignCenter, QString::number(item.pageIndex + 1));
PDFRenderer renderer(m_controller->getDocument());
QList<PDFRenderError> errors = renderer.render(painter, placedRect, item.pageIndex);
}
}
}

View File

@ -84,6 +84,9 @@ public:
/// \param blockIndex Index of the block
LayoutItems getLayoutItems(size_t blockIndex) const;
/// Returns the document
const PDFDocument* getDocument() const { return m_document; }
signals:
void drawSpaceChanged();

View File

@ -0,0 +1,117 @@
// 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 PDFFLATARRAY_H
#define PDFFLATARRAY_H
#include <QtGlobal>
#include <array>
#include <vector>
#include <algorithm>
namespace pdf
{
/// This represents a fast array, consisting of "fast" block of fixed size \p FlatSize,
/// and "slow" block of variable size. Usually, this array is used when vast majority
/// of usage size is below FlatSize, only minority is above FlatSize. Typical example
/// of use of this class:
///
/// We have colors in PDF, which can have usually 1, 3 or 4 color components. But in some
/// rare cases, we have much more components, for example for DeviceN color spaces.
/// For this reason, we will set FlatSize to 4 (so Gray, RGB and CMYK colors will not
/// use slow "variable" part).
template<typename T, size_t FlatSize>
class PDFFlatArray
{
public:
explicit PDFFlatArray() :
m_flatBlock(),
m_flatBlockEndIterator(m_flatBlock.begin()),
m_variableBlock()
{
}
template<typename... Arguments, typename std::enable_if<sizeof...(Arguments) < FlatSize, int>::type = 0>
explicit inline PDFFlatArray(Arguments... arguments) :
m_flatBlock(arguments...),
m_flatBlockEndIterator(std::next(m_flatBlock.begin(), sizeof...(Arguments))),
m_variableBlock()
{
}
/// Returns the size of the array
size_t size() const { return getFlatBlockSize() + m_variableBlock.size(); }
/// Returns true, if array is empty
bool empty() const { return size() == 0; }
template<size_t index>
const T& get() const
{
if constexpr (index < FlatSize)
{
return m_flatBlock[size];
}
else
{
return m_variableBlock[size - FlatSize];
}
}
template<size_t index>
T& get()
{
if constexpr (index < FlatSize)
{
return m_flatBlock[size];
}
else
{
return m_variableBlock[size - FlatSize];
}
}
const T& operator[] (size_t index) const
{
Q_ASSERT(index < size());
if (index < FlatSize)
{
return m_flatBlock[index];
}
else
{
return m_variableBlock[index - FlatSize];
}
}
private:
size_t getFlatBlockSize() const { return std::distance(m_flatBlock.cbegin(), std::array<T, FlatSize>::const_iterator(m_flatBlockEndIterator)); }
std::array<T, FlatSize> m_flatBlock;
typename std::array<T, FlatSize>::iterator m_flatBlockEndIterator; ///< Pointer to the end of flat block
std::vector<T> m_variableBlock;
};
} // namespace pdf
#endif // PDFFLATARRAY_H

View File

@ -82,6 +82,7 @@ public:
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; }

View File

@ -0,0 +1,123 @@
// 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 "pdfrenderer.h"
#include "pdfdocument.h"
namespace pdf
{
PDFRenderer::PDFRenderer(const PDFDocument* document) :
m_document(document),
m_features(Antialasing | TextAntialiasing)
{
Q_ASSERT(document);
}
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const
{
Q_UNUSED(painter);
Q_UNUSED(rectangle);
const PDFCatalog* catalog = m_document->getCatalog();
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
{
// Invalid page index
return { PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 doesn't exist.").arg(pageIndex + 1)) };
}
const PDFPage* page = catalog->getPage(pageIndex);
Q_ASSERT(page);
PDFPageContentProcessor processor(page, m_document);
return processor.processContents();
}
PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document) :
m_page(page),
m_document(document)
{
Q_ASSERT(page);
Q_ASSERT(document);
}
QList<PDFRenderError> PDFPageContentProcessor::processContents()
{
const PDFObject& contents = m_page->getContents();
if (contents.isArray())
{
const PDFArray* array = contents.getArray();
const size_t count = array->getCount();
QList<PDFRenderError> errors;
for (size_t i = 0; i < count; ++i)
{
const PDFObject& streamObject = m_document->getObject(array->getItem(i));
if (streamObject.isStream())
{
errors.append(processContentStream(streamObject.getStream()));
}
else
{
errors.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page contents.")));
}
}
return std::move(errors);
}
else if (contents.isStream())
{
return processContentStream(contents.getStream());
}
else
{
return { PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page contents.")) };
}
}
QList<PDFRenderError> PDFPageContentProcessor::processContentStream(const PDFStream* stream)
{
QByteArray content = m_document->getDecodedStream(stream);
return QList<PDFRenderError>();
}
PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() :
m_currentTransformationMatrix(),
m_fillColorSpace(),
m_strokeColorSpace(),
m_fillColor(Qt::black),
m_strokeColor(Qt::black),
m_lineWidth(1.0),
m_lineCapStyle(Qt::FlatCap),
m_lineJoinStyle(Qt::MiterJoin),
m_mitterLimit(10.0),
m_renderingIntent(),
m_flatness(1.0),
m_smoothness(0.01)
{
m_fillColorSpace.reset(new PDFDeviceGrayColorSpace);
m_strokeColorSpace = m_fillColorSpace;
}
PDFPageContentProcessor::PDFPageContentProcessorState::~PDFPageContentProcessorState()
{
}
} // namespace pdf

View File

@ -0,0 +1,129 @@
// 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 PDFRENDERER_H
#define PDFRENDERER_H
#include "pdfpage.h"
#include "pdfcolorspaces.h"
#include <QMatrix>
#include <QSharedPointer>
#include <stack>
namespace pdf
{
enum RenderErrorType
{
Error,
NotImplemented
};
struct PDFRenderError
{
explicit PDFRenderError() = default;
explicit PDFRenderError(RenderErrorType type, QString message) :
type(type),
message(std::move(message))
{
}
RenderErrorType type = RenderErrorType::Error;
QString message;
};
/// Renders the PDF page on the painter, or onto an image.
class PDFRenderer
{
public:
explicit PDFRenderer(const PDFDocument* document);
enum Feature
{
Antialasing, ///< Antialiasing for lines, shapes, etc.
TextAntialiasing, ///< Antialiasing for drawing text
SmoothImages ///< Adjust images to the device space using smooth transformation (slower, but better performance quality)
};
Q_DECLARE_FLAGS(Features, Feature)
/// Paints desired page onto the painter. Page is painted in the rectangle using best-fit method.
/// If the page doesn't exist, then error is returned. No exception is thrown. Rendering errors
/// are reported and returned in the error list. If no error occured, empty list is returned.
/// \param painter Painter
/// \param rectangle Paint area for the page
/// \param pageIndex Index of the page to be painted
QList<PDFRenderError> render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const;
private:
const PDFDocument* m_document;
Features m_features;
};
/// Process the contents of the page.
class PDFPageContentProcessor
{
public:
explicit PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document);
/// Process the contents of the page
QList<PDFRenderError> processContents();
protected:
/// Process the content stream
QList<PDFRenderError> processContentStream(const PDFStream* stream);
/// Represents graphic state of the PDF (holding current graphic state parameters).
/// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State"
class PDFPageContentProcessorState
{
public:
explicit PDFPageContentProcessorState();
~PDFPageContentProcessorState();
private:
QMatrix m_currentTransformationMatrix;
QSharedPointer<PDFAbstractColorSpace> m_fillColorSpace;
QSharedPointer<PDFAbstractColorSpace> m_strokeColorSpace;
QColor m_fillColor;
QColor m_strokeColor;
PDFReal m_lineWidth;
Qt::PenCapStyle m_lineCapStyle;
Qt::PenJoinStyle m_lineJoinStyle;
PDFReal m_mitterLimit;
QByteArray m_renderingIntent;
PDFReal m_flatness;
PDFReal m_smoothness;
};
private:
const PDFPage* m_page;
const PDFDocument* m_document;
/// Stack with current graphic states
std::stack<PDFPageContentProcessorState> m_stack;
};
} // namespace pdf
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)
#endif // PDFRENDERER_H

View File

@ -401,4 +401,46 @@ QByteArray PDFRunLengthDecodeFilter::apply(const QByteArray& data, const PDFDocu
return result;
}
const PDFStreamFilter* PDFStreamFilterStorage::getFilter(const QByteArray& filterName)
{
const PDFStreamFilterStorage* instance = getInstance();
auto it = instance->m_filters.find(filterName);
if (it != instance->m_filters.cend())
{
return it->second.get();
}
auto itNameDecoded = instance->m_abbreviations.find(filterName);
if (itNameDecoded != instance->m_abbreviations.cend())
{
return getFilter(itNameDecoded->second);
}
return nullptr;
}
PDFStreamFilterStorage::PDFStreamFilterStorage()
{
// Initialize map with the filters
m_filters["ASCIIHexDecode"] = std::make_unique<PDFAsciiHexDecodeFilter>();
m_filters["ASCII85Decode"] = std::make_unique<PDFAscii85DecodeFilter>();
m_filters["LZWDecode"] = std::make_unique<PDFLzwDecodeFilter>();
m_filters["FlateDecode"] = std::make_unique<PDFFlateDecodeFilter>();
m_filters["RunLengthDecode"] = std::make_unique<PDFRunLengthDecodeFilter>();
m_abbreviations["AHx"] = "ASCIIHexDecode";
m_abbreviations["A85"] = "ASCII85Decode";
m_abbreviations["LZW"] = "LZWDecode";
m_abbreviations["Fl"] = "FlateDecode";
m_abbreviations["RL"] = "RunLengthDecode";
m_abbreviations["CCF"] = "CCITFaxDecode";
m_abbreviations["DCT"] = "DCTDecode";
}
const PDFStreamFilterStorage* PDFStreamFilterStorage::getInstance()
{
static PDFStreamFilterStorage instance;
return &instance;
}
} // namespace pdf

View File

@ -22,9 +22,35 @@
#include <QByteArray>
#include <memory>
namespace pdf
{
class PDFDocument;
class PDFStreamFilter;
/// Storage for stream filters. Can retrieve stream filters by name. Using singleton
/// design pattern. Use static methods to retrieve filters.
class PDFStreamFilterStorage
{
public:
/// Retrieves filter by filter name. If filter with that name doesn't exist,
/// then nullptr is returned. This function is thread safe.
/// \param filterName Name of the filter to be retrieved.
static const PDFStreamFilter* getFilter(const QByteArray& filterName);
private:
explicit PDFStreamFilterStorage();
static const PDFStreamFilterStorage* getInstance();
/// Maps names to the instances of the stream filters
std::map<QByteArray, std::unique_ptr<PDFStreamFilter>> m_filters;
/// Filter stream names can be specified in simplified (shorter) form.
/// This map maps shorter form to the longer form.
std::map<QByteArray, QByteArray> m_abbreviations;
};
class PDFFORQTLIBSHARED_EXPORT PDFStreamFilter
{