XFA: rendering of rectangle elements and borders

This commit is contained in:
Jakub Melka 2021-11-20 18:42:25 +01:00
parent 6b7e839618
commit aab5c41e12
5 changed files with 465 additions and 5 deletions

View File

@ -1330,12 +1330,12 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
Q_ASSERT(page);
const PageAnnotations& annotations = getPageAnnotations(pageIndex);
if (!annotations.isEmpty())
{
const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
Q_ASSERT(page);
PDFRenderer::Features features = m_features;
if (!features.testFlag(PDFRenderer::DisplayAnnotations))
{
@ -1377,6 +1377,12 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
m_fontCache->setCacheShrinkEnabled(&fontCacheLock, true);
}
// Draw XFA form
if (m_formManager)
{
m_formManager->drawXFAForm(pagePointToDevicePointMatrix, page, errors, painter);
}
}
void PDFAnnotationManager::drawAnnotation(const PageAnnotation& annotation,

View File

@ -1657,6 +1657,17 @@ const std::optional<QCursor>& PDFFormManager::getCursor() const
return m_mouseCursor;
}
void PDFFormManager::drawXFAForm(const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
QList<PDFRenderError>& errors,
QPainter* painter)
{
if (hasXFAForm())
{
m_xfaEngine.draw(pagePointToDevicePointMatrix, page, errors, painter);
}
}
void PDFFormManager::clearEditors()
{
qDeleteAll(m_widgetEditors);

View File

@ -677,11 +677,21 @@ public:
/// Returns current cursor
virtual const std::optional<QCursor>& getCursor() const override;
/// Draws XFA form, or does nothing, if XFA form is not present
/// \param pagePointToDevicePointMatrix Page point to device point matrix
/// \param page Page
/// \param errors Error list (for reporting rendering errors)
/// \param painter Painter
void drawXFAForm(const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
QList<PDFRenderError>& errors,
QPainter* painter);
virtual int getInputPriority() const override { return FormPriority; }
signals:
void actionTriggered(const PDFAction* action);
void documentModified(PDFModifiedDocument document);
void actionTriggered(const pdf::PDFAction* action);
void documentModified(pdf::PDFModifiedDocument document);
private:
void updateFormWidgetEditors();

View File

@ -17,9 +17,11 @@
#include "pdfxfaengine.h"
#include "pdfform.h"
#include "pdfpainterutils.h"
#include <QDomElement>
#include <QDomDocument>
#include <QStringList>
#include <stack>
#include <optional>
@ -9623,6 +9625,11 @@ public:
void setLayoutItems(PDFInteger pageIndex, LayoutItems layoutItems) { m_layout.layoutItems[pageIndex] = std::move(layoutItems); }
void draw(const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
QList<PDFRenderError>& errors,
QPainter* painter);
private:
struct Layout
@ -9632,6 +9639,39 @@ private:
void clear();
QMarginsF createMargin(const xfa::XFA_margin* margin);
QColor createColor(const xfa::XFA_color* color) const;
QPen createPenFromEdge(const xfa::XFA_edge* edge, QList<PDFRenderError>& errors) const;
void drawItemBorder(const xfa::XFA_border* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
QPainter* painter);
void drawItemFill(const xfa::XFA_fill* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
QPainter* painter);
void drawItemRectEdges(const std::vector<xfa::XFA_Node<xfa::XFA_edge>>& edges,
const std::vector<xfa::XFA_Node<xfa::XFA_corner>>& corners,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
const xfa::XFA_BaseNode::HAND hand,
QPainter* painter);
void drawItemDraw(const xfa::XFA_draw* item,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
QPainter* painter);
void drawItemField(const xfa::XFA_field* item,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
QPainter* painter);
xfa::XFA_Node<xfa::XFA_template> m_template;
const PDFDocument* m_document;
PDFForm* m_form;
@ -10674,6 +10714,14 @@ void PDFXFAEngine::setDocument(const PDFModifiedDocument& document, PDFForm* for
m_impl->setDocument(document, form);
}
void PDFXFAEngine::draw(const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
QList<PDFRenderError>& errors,
QPainter* painter)
{
m_impl->draw(pagePointToDevicePointMatrix, page, errors, painter);
}
PDFXFAEngineImpl::PDFXFAEngineImpl() :
m_document(nullptr),
m_form(nullptr)
@ -10743,6 +10791,278 @@ void PDFXFAEngineImpl::setDocument(const PDFModifiedDocument& document, PDFForm*
}
}
void PDFXFAEngineImpl::draw(const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
QList<PDFRenderError>& errors,
QPainter* painter)
{
if (!m_document || m_layout.layoutItems.empty())
{
// Nothing to draw
return;
}
PDFInteger pageIndex = m_document->getCatalog()->getPageIndexFromPageReference(page->getPageReference());
auto it = m_layout.layoutItems.find(pageIndex);
if (it == m_layout.layoutItems.cend())
{
// Nothing to draw, page is not present
return;
}
PDFPainterStateGuard guard(painter);
painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->translate(0, page->getMediaBox().height());
painter->scale(1.0, -1.0);
const LayoutItems& items = it->second;
for (const LayoutItem& item : items)
{
drawItemDraw(item.draw, errors, item.nominalExtent, painter);
drawItemField(item.field, errors, item.nominalExtent, painter);
}
}
void PDFXFAEngineImpl::drawItemDraw(const xfa::XFA_draw* item,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
QPainter* painter)
{
if (!item)
{
// Not a draw
return;
}
QRectF nominalExtent = nominalExtentArea;
QRectF nominalContentArea = nominalExtent;
QMarginsF contentMargins = createMargin(item->getMargin());
nominalContentArea = nominalExtent.marginsRemoved(contentMargins);
drawItemBorder(item->getBorder(), errors, nominalExtent, painter);
if (const xfa::XFA_value* value = item->getValue())
{
if (const xfa::XFA_rectangle* rectangle = value->getRectangle())
{
drawItemFill(rectangle->getFill(), errors, nominalContentArea, painter);
drawItemRectEdges(rectangle->getEdge(), rectangle->getCorner(), errors, nominalContentArea, rectangle->getHand(), painter);
}
// TODO: implement draw value
}
}
void PDFXFAEngineImpl::drawItemField(const xfa::XFA_field* item,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
QPainter* painter)
{
// TODO: implement this
}
void PDFXFAEngineImpl::drawItemBorder(const xfa::XFA_border* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
QPainter* painter)
{
if (!item || item->getPresence() != xfa::XFA_BaseNode::PRESENCE::Visible)
{
return;
}
QMarginsF contentMargins = createMargin(item->getMargin());
nominalContentArea = nominalContentArea.marginsRemoved(contentMargins);
if (nominalContentArea.isEmpty())
{
// Jakub Melka: nothing to draw
return;
}
drawItemFill(item->getFill(), errors, nominalContentArea, painter);
drawItemRectEdges(item->getEdge(), item->getCorner(), errors, nominalContentArea, item->getHand(), painter);
}
void PDFXFAEngineImpl::drawItemFill(const xfa::XFA_fill* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
QPainter* painter)
{
if (!item)
{
return;
}
QColor startColor = createColor(item->getColor());
if (!startColor.isValid())
{
startColor = Qt::white;
}
if (item->getSolid())
{
painter->fillRect(nominalContentArea, startColor);
}
else if (const xfa::XFA_linear* linear = item->getLinear())
{
QLinearGradient linearGradient;
switch (linear->getType())
{
case pdf::xfa::XFA_BaseNode::TYPE1::ToRight:
linearGradient.setStart(nominalContentArea.topLeft());
linearGradient.setFinalStop(nominalContentArea.topRight());
break;
case pdf::xfa::XFA_BaseNode::TYPE1::ToBottom:
linearGradient.setStart(nominalContentArea.topLeft());
linearGradient.setFinalStop(nominalContentArea.bottomLeft());
break;
case pdf::xfa::XFA_BaseNode::TYPE1::ToLeft:
linearGradient.setStart(nominalContentArea.topRight());
linearGradient.setFinalStop(nominalContentArea.topLeft());
break;
case pdf::xfa::XFA_BaseNode::TYPE1::ToTop:
linearGradient.setStart(nominalContentArea.bottomLeft());
linearGradient.setFinalStop(nominalContentArea.topLeft());
break;
}
QColor endColor = createColor(linear->getColor());
linearGradient.setColorAt(0.0, startColor);
linearGradient.setColorAt(1.0, endColor);
painter->fillRect(nominalContentArea, QBrush(std::move(linearGradient)));
}
else if (const xfa::XFA_radial* radial = item->getRadial())
{
QRadialGradient radialGradient(nominalContentArea.center(), qMin(nominalContentArea.width(), nominalContentArea.height()));
QColor endColor = createColor(radial->getColor());
switch (radial->getType())
{
case pdf::xfa::XFA_BaseNode::TYPE3::ToEdge:
radialGradient.setColorAt(0.0, startColor);
radialGradient.setColorAt(1.0, endColor);
break;
case pdf::xfa::XFA_BaseNode::TYPE3::ToCenter:
radialGradient.setColorAt(1.0, startColor);
radialGradient.setColorAt(0.0, endColor);
break;
}
painter->fillRect(nominalContentArea, QBrush(std::move(radialGradient)));
}
else if (item->getPattern() || item->getStipple())
{
errors << PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("XFA: Unknown fill pattern."));
}
else
{
painter->fillRect(nominalContentArea, startColor);
}
}
void PDFXFAEngineImpl::drawItemRectEdges(const std::vector<xfa::XFA_Node<xfa::XFA_edge>>& edges,
const std::vector<xfa::XFA_Node<xfa::XFA_corner>>& corners,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
const xfa::XFA_BaseNode::HAND hand,
QPainter* painter)
{
if (edges.empty())
{
return;
}
constexpr size_t LINE_COUNT = 4;
std::array<xfa::XFA_Node<xfa::XFA_edge>, LINE_COUNT> fourEdges = { edges.back(), edges.back(), edges.back(), edges.back() };
for (size_t i = 0; i < edges.size(); ++i)
{
if (i >= fourEdges.size())
{
break;
}
fourEdges[i] = edges[i];
}
std::array<QPen, LINE_COUNT> edgePen = {
createPenFromEdge(fourEdges[0].getValue(), errors),
createPenFromEdge(fourEdges[1].getValue(), errors),
createPenFromEdge(fourEdges[2].getValue(), errors),
createPenFromEdge(fourEdges[3].getValue(), errors)
};
// All lines are equal? If yes, just draw the rectangle using first pen. If lines
// are not equal, then we must draw each line separately.
if (std::adjacent_find(edgePen.begin(), edgePen.end(), std::not_equal_to()) == edgePen.end())
{
QPen pen = edgePen.front();
QRectF lineRectangle = nominalContentArea;
qreal penWidthHalf = pen.widthF() * 0.5;
QMarginsF margins(penWidthHalf, penWidthHalf, penWidthHalf, penWidthHalf);
switch (hand)
{
case pdf::xfa::XFA_BaseNode::HAND::Even:
break;
case pdf::xfa::XFA_BaseNode::HAND::Left:
lineRectangle = lineRectangle.marginsAdded(margins);
break;
case pdf::xfa::XFA_BaseNode::HAND::Right:
lineRectangle = lineRectangle.marginsRemoved(margins);
break;
}
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(lineRectangle);
}
else
{
std::array<QLineF, LINE_COUNT> lines = {
QLineF(nominalContentArea.topLeft(), nominalContentArea.topRight()),
QLineF(nominalContentArea.topRight(), nominalContentArea.bottomRight()),
QLineF(nominalContentArea.bottomRight(), nominalContentArea.bottomLeft()),
QLineF(nominalContentArea.bottomLeft(), nominalContentArea.topLeft())
};
for (size_t i = 0; i < LINE_COUNT; ++i)
{
PDFReal offset = 0.0;
switch (hand)
{
case pdf::xfa::XFA_BaseNode::HAND::Even:
break;
case pdf::xfa::XFA_BaseNode::HAND::Left:
offset = -edgePen[i].widthF() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::HAND::Right:
offset = +edgePen[i].widthF() * 0.5;
break;
}
if (!qFuzzyIsNull(offset))
{
QLineF& line = lines[i];
QLineF normal = line.normalVector().unitVector();
line.translate(normal.dx() * offset, normal.dy() * offset);
}
painter->setPen(edgePen[i]);
painter->setBrush(Qt::NoBrush);
painter->drawLine(lines[i]);
}
}
if (!corners.empty())
{
errors << PDFRenderError(RenderErrorType::NotSupported, PDFTranslationContext::tr("XFA: visual display of the corners of rectangle are not supported."));
}
}
void PDFXFAEngineImpl::clear()
{
// Clear the template
@ -10750,6 +11070,108 @@ void PDFXFAEngineImpl::clear()
m_layout = Layout();
}
QMarginsF PDFXFAEngineImpl::createMargin(const xfa::XFA_margin* margin)
{
if (!margin)
{
return QMarginsF();
}
const PDFReal leftMargin = margin->getLeftInset().getValuePt(nullptr);
const PDFReal topMargin = margin->getTopInset().getValuePt(nullptr);
const PDFReal rightMargin = margin->getRightInset().getValuePt(nullptr);
const PDFReal bottomMargin = margin->getBottomInset().getValuePt(nullptr);
QMarginsF margins(leftMargin, topMargin, rightMargin, bottomMargin);
return margins;
}
QColor PDFXFAEngineImpl::createColor(const xfa::XFA_color* color) const
{
if (color)
{
QStringList sl = color->getValue().split(",");
const int r = sl.size() > 0 ? sl[0].toInt() : 255;
const int g = sl.size() > 1 ? sl[1].toInt() : 255;
const int b = sl.size() > 2 ? sl[2].toInt() : 255;
return QColor(r, g, b, 255);
}
return QColor();
}
QPen PDFXFAEngineImpl::createPenFromEdge(const xfa::XFA_edge* edge, QList<PDFRenderError>& errors) const
{
QPen pen;
if (!edge)
{
return pen;
}
switch (edge->getCap())
{
case pdf::xfa::XFA_BaseNode::CAP::Square:
pen.setCapStyle(Qt::SquareCap);
break;
case pdf::xfa::XFA_BaseNode::CAP::Butt:
pen.setCapStyle(Qt::FlatCap);
break;
case pdf::xfa::XFA_BaseNode::CAP::Round:
pen.setCapStyle(Qt::RoundCap);
break;
}
switch (edge->getStroke())
{
case pdf::xfa::XFA_BaseNode::STROKE::Solid:
pen.setStyle(Qt::SolidLine);
break;
case pdf::xfa::XFA_BaseNode::STROKE::DashDot:
pen.setStyle(Qt::DashDotLine);
break;
case pdf::xfa::XFA_BaseNode::STROKE::DashDotDot:
pen.setStyle(Qt::DashDotDotLine);
break;
case pdf::xfa::XFA_BaseNode::STROKE::Dashed:
pen.setStyle(Qt::DashLine);
break;
case pdf::xfa::XFA_BaseNode::STROKE::Dotted:
pen.setStyle(Qt::DotLine);
break;
case pdf::xfa::XFA_BaseNode::STROKE::Embossed:
case pdf::xfa::XFA_BaseNode::STROKE::Etched:
case pdf::xfa::XFA_BaseNode::STROKE::Lowered:
case pdf::xfa::XFA_BaseNode::STROKE::Raised:
pen.setStyle(Qt::SolidLine); // Ignore these line types
errors << PDFRenderError(RenderErrorType::NotSupported, PDFTranslationContext::tr("XFA: special stroke is not supported."));
break;
}
pen.setWidthF(edge->getThickness().getValuePt(nullptr));
QColor color = createColor(edge->getColor());
if (color.isValid())
{
const xfa::XFA_BaseNode::PRESENCE presence = edge->getPresence();
switch (presence)
{
case pdf::xfa::XFA_BaseNode::PRESENCE::Visible:
color.setAlphaF(1.0);
break;
case pdf::xfa::XFA_BaseNode::PRESENCE::Hidden:
case pdf::xfa::XFA_BaseNode::PRESENCE::Inactive:
case pdf::xfa::XFA_BaseNode::PRESENCE::Invisible:
color.setAlphaF(0.0);
break;
}
}
pen.setColor(color);
return pen;
}
template<typename Node>
PDFXFALayoutEngine::SizeInfo PDFXFALayoutEngine::getSizeInfo(const Node* node) const
{

View File

@ -20,6 +20,7 @@
#include "pdfglobal.h"
#include "pdfdocument.h"
#include "pdfexception.h"
#include <memory>
@ -37,6 +38,16 @@ public:
void setDocument(const PDFModifiedDocument& document, PDFForm* form);
/// Draws XFA form
/// \param pagePointToDevicePointMatrix Page point to device point matrix
/// \param page Page
/// \param errors Error list (for reporting rendering errors)
/// \param painter Painter
void draw(const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
QList<PDFRenderError>& errors,
QPainter* painter);
private:
std::unique_ptr<PDFXFAEngineImpl> m_impl;
};