XFA: draw text nodes

This commit is contained in:
Jakub Melka 2021-11-28 19:05:35 +01:00
parent 1c2aff30a3
commit 1656f3b874
1 changed files with 323 additions and 1 deletions

View File

@ -22,6 +22,8 @@
#include <QDomElement>
#include <QDomDocument>
#include <QStringList>
#include <QDateTime>
#include <QImageReader>
#include <stack>
#include <optional>
@ -9654,6 +9656,7 @@ public:
QRectF nominalExtent;
const xfa::XFA_draw* draw = nullptr;
const xfa::XFA_field* field = nullptr;
size_t paragraphSettingsIndex = 0;
};
using LayoutItems = std::vector<LayoutItem>;
@ -9684,6 +9687,15 @@ private:
QPen createPenFromEdge(const std::vector<xfa::XFA_Node<xfa::XFA_edge>>& edges, size_t index, QList<PDFRenderError>& errors) const;
QPen createPenFromCorner(const std::vector<xfa::XFA_Node<xfa::XFA_corner>>& corners, size_t index, QList<PDFRenderError>& errors) const;
struct NodeValue
{
QVariant value;
PDFInteger hintTextMaxChars = 0;
PDFInteger hintFloatFracDigits = 2;
PDFInteger hintFloatLeadDigits = -1;
bool hintTextHtml = false;
};
void drawItemBorder(const xfa::XFA_border* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
@ -9694,6 +9706,11 @@ private:
QRectF nominalContentArea,
QPainter* painter);
void drawItemLine(const xfa::XFA_line* 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,
@ -9704,6 +9721,7 @@ private:
void drawItemDraw(const xfa::XFA_draw* item,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
size_t paragraphSettingsIndex,
QPainter* painter);
void drawItemField(const xfa::XFA_field* item,
@ -9711,6 +9729,19 @@ private:
QRectF nominalExtentArea,
QPainter* painter);
void drawUi(const xfa::XFA_ui* ui,
const NodeValue& value,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
size_t paragraphSettingsIndex,
QPainter* painter);
void drawUiTextEdit(const xfa::XFA_textEdit* textEdit,
const NodeValue& value,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
size_t paragraphSettingsIndex,
QPainter* painter);
xfa::XFA_Node<xfa::XFA_template> m_template;
const PDFDocument* m_document;
@ -10541,6 +10572,7 @@ void PDFXFALayoutEngine::performLayout(PDFXFAEngineImpl* engine, const xfa::XFA_
engineLayoutItem.nominalExtent = layoutItem.nominalExtent;
engineLayoutItem.draw = layoutItem.draw;
engineLayoutItem.field = layoutItem.field;
engineLayoutItem.paragraphSettingsIndex = layoutItem.paragraphSettingsIndex;
layoutItems.emplace_back(std::move(engineLayoutItem));
}
}
@ -11273,7 +11305,7 @@ void PDFXFAEngineImpl::draw(const QMatrix& pagePointToDevicePointMatrix,
const LayoutItems& items = it->second;
for (const LayoutItem& item : items)
{
drawItemDraw(item.draw, errors, item.nominalExtent, painter);
drawItemDraw(item.draw, errors, item.nominalExtent, item.paragraphSettingsIndex, painter);
drawItemField(item.field, errors, item.nominalExtent, painter);
}
}
@ -11281,6 +11313,7 @@ void PDFXFAEngineImpl::draw(const QMatrix& pagePointToDevicePointMatrix,
void PDFXFAEngineImpl::drawItemDraw(const xfa::XFA_draw* item,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
size_t paragraphSettingsIndex,
QPainter* painter)
{
if (!item)
@ -11298,12 +11331,135 @@ void PDFXFAEngineImpl::drawItemDraw(const xfa::XFA_draw* item,
if (const xfa::XFA_value* value = item->getValue())
{
std::optional<NodeValue> nodeValue;
if (const xfa::XFA_rectangle* rectangle = value->getRectangle())
{
drawItemFill(rectangle->getFill(), errors, nominalContentArea, painter);
drawItemRectEdges(rectangle->getEdge(), rectangle->getCorner(), errors, nominalContentArea, rectangle->getHand(), painter);
}
else if (const xfa::XFA_line* line = value->getLine())
{
drawItemLine(line, errors, nominalContentArea, painter);
}
else if (const xfa::XFA_text* text = value->getText())
{
nodeValue.emplace();
nodeValue->value = text->getNodeValue() ? *text->getNodeValue() : QString();
nodeValue->hintTextMaxChars = text->getMaxChars();
}
else if (const xfa::XFA_exData* exData = value->getExData())
{
if (exData->getContentType() == "text/html")
{
nodeValue.emplace();
nodeValue->value = exData->getNodeValue() ? *exData->getNodeValue() : QString();
nodeValue->hintTextMaxChars = exData->getMaxLength();
nodeValue->hintTextHtml = true;
}
else
{
errors << PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Rendering of content type '%1' is not implemented.").arg(exData->getContentType()));
}
}
else if (const xfa::XFA_integer* integer = value->getInteger())
{
QString textValue = integer->getNodeValue() ? *integer->getNodeValue() : QString();
nodeValue.emplace();
nodeValue->value = textValue.toInt();
}
else if (const xfa::XFA_boolean* boolean = value->getBoolean())
{
QString textValue = boolean->getNodeValue() ? *boolean->getNodeValue() : QString();
nodeValue.emplace();
nodeValue->value = bool(textValue.toInt() != 0);
}
else if (const xfa::XFA_decimal* decimal = value->getDecimal())
{
QString textValue = decimal->getNodeValue() ? *decimal->getNodeValue() : QString();
nodeValue.emplace();
nodeValue->hintFloatFracDigits = decimal->getFracDigits();
nodeValue->hintFloatLeadDigits = decimal->getLeadDigits();
nodeValue->value = textValue.toDouble();
}
else if (const xfa::XFA_float* floatItem = value->getFloat())
{
QString textValue = floatItem->getNodeValue() ? *floatItem->getNodeValue() : QString();
nodeValue.emplace();
nodeValue->hintFloatFracDigits = -1;
nodeValue->hintFloatLeadDigits = -1;
nodeValue->value = textValue.toDouble();
}
else if (const xfa::XFA_dateTime* dateTime = value->getDateTime())
{
QString textValue = dateTime->getNodeValue() ? *dateTime->getNodeValue() : QString();
QDateTime dateTimeVal = QDateTime::fromString(textValue, Qt::ISODate);
if (dateTimeVal.isValid())
{
nodeValue.emplace();
nodeValue->value = std::move(dateTimeVal);
}
}
else if (const xfa::XFA_date* date = value->getDate())
{
QString textValue = date->getNodeValue() ? *date->getNodeValue() : QString();
QDate dateVal = QDate::fromString(textValue, Qt::ISODate);
if (dateVal.isValid())
{
nodeValue.emplace();
nodeValue->value = std::move(dateVal);
}
}
else if (const xfa::XFA_time* time = value->getTime())
{
QString textValue = time->getNodeValue() ? *time->getNodeValue() : QString();
QTime timeVal = QTime::fromString(textValue, Qt::ISODate);
if (timeVal.isValid())
{
nodeValue.emplace();
nodeValue->value = std::move(timeVal);
}
}
else if (const xfa::XFA_image* image = value->getImage())
{
QByteArray ba;
QString textValue = image->getNodeValue() ? *image->getNodeValue() : QString();
switch (image->getTransferEncoding())
{
case pdf::xfa::XFA_BaseNode::TRANSFERENCODING1::Base64:
ba = QByteArray::fromBase64(textValue.toLatin1());
break;
case pdf::xfa::XFA_BaseNode::TRANSFERENCODING1::None:
ba = textValue.toLatin1();
break;
case pdf::xfa::XFA_BaseNode::TRANSFERENCODING1::Package:
errors << PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image encoded by 'package' mode not decoded."));
break;
}
QBuffer buffer(&ba);
QImageReader reader(&buffer);
reader.setDecideFormatFromContent(true);
QImage imageValue = reader.read();
if (!imageValue.isNull())
{
nodeValue.emplace();
nodeValue->value = std::move(imageValue);
}
else
{
errors << PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image of type '%1' not decoded.").arg(image->getContentType()));
}
}
if (nodeValue)
{
drawUi(item->getUi(), nodeValue.value(), errors, nominalContentArea, paragraphSettingsIndex, painter);
}
// TODO: implement draw arc (getArc())
// TODO: implement draw value
}
}
@ -11329,6 +11485,119 @@ void PDFXFAEngineImpl::drawItemField(const xfa::XFA_field* item,
// TODO: implement this
}
void PDFXFAEngineImpl::drawUi(const xfa::XFA_ui* ui,
const NodeValue& value,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
size_t paragraphSettingsIndex,
QPainter* painter)
{
if (!value.value.isValid())
{
return;
}
const xfa::XFA_barcode* barcode = nullptr;
const xfa::XFA_button* button = nullptr;
const xfa::XFA_checkButton* checkButton = nullptr;
const xfa::XFA_choiceList* choiceList = nullptr;
const xfa::XFA_dateTimeEdit* dateTimeEdit = nullptr;
const xfa::XFA_defaultUi* defaultUi = nullptr;
const xfa::XFA_imageEdit* imageEdit = nullptr;
const xfa::XFA_numericEdit* numericEdit = nullptr;
const xfa::XFA_passwordEdit* passwordEdit = nullptr;
const xfa::XFA_signature* signature = nullptr;
const xfa::XFA_textEdit* textEdit = nullptr;
if (ui)
{
barcode = ui->getBarcode();
button = ui->getButton();
checkButton = ui->getCheckButton();
choiceList = ui->getChoiceList();
dateTimeEdit = ui->getDateTimeEdit();
defaultUi = ui->getDefaultUi();
imageEdit = ui->getImageEdit();
numericEdit = ui->getNumericEdit();
passwordEdit = ui->getPasswordEdit();
signature = ui->getSignature();
textEdit = ui->getTextEdit();
}
const bool isNonDefaultUi = barcode || button || checkButton || choiceList ||
dateTimeEdit || imageEdit || numericEdit ||
passwordEdit || signature || textEdit;
const bool isDefaultUi = !isNonDefaultUi || defaultUi;
if (textEdit || (isDefaultUi && value.value.type() == QVariant::String))
{
drawUiTextEdit(textEdit, value, errors, nominalExtentArea, paragraphSettingsIndex, painter);
}
// TODO: implement all ui
}
void PDFXFAEngineImpl::drawUiTextEdit(const xfa::XFA_textEdit* textEdit,
const NodeValue& value,
QList<PDFRenderError>& errors,
QRectF nominalExtentArea,
size_t paragraphSettingsIndex,
QPainter* painter)
{
QRectF nominalExtent = nominalExtentArea;
QRectF nominalContentArea = nominalExtent;
QMarginsF contentMargins = textEdit ? createMargin(textEdit->getMargin()) : QMarginsF();
nominalContentArea = nominalExtent.marginsRemoved(contentMargins);
bool isComb = false;
bool isMultiline = true;
bool isRichTextAllowed = value.hintTextHtml;
PDFInteger combNumberOfCells = value.hintTextMaxChars;
if (textEdit)
{
isMultiline = textEdit->getMultiLine() == xfa::XFA_textEdit::MULTILINE::_1;
isRichTextAllowed = textEdit->getAllowRichText();
if (const xfa::XFA_comb* comb = textEdit->getComb())
{
isComb = true;
PDFInteger combCells = comb->getNumberOfCells();
if (combCells > 0)
{
combNumberOfCells = combCells;
}
}
drawItemBorder(textEdit->getBorder(), errors, nominalExtent, painter);
}
combNumberOfCells = qMax(combNumberOfCells, PDFInteger(1));
const xfa::XFA_ParagraphSettings& settings = m_layout.paragraphSettings.at(paragraphSettingsIndex);
if (!isComb)
{
painter->setFont(settings.getFont());
QRectF textRect = nominalContentArea;
textRect = textRect.marginsRemoved(settings.getMargins());
if (!isRichTextAllowed)
{
const int textFlag = isMultiline ? Qt::TextWordWrap : Qt::TextSingleLine;
painter->drawText(textRect, textFlag | int(settings.getAlignment()), value.value.toString());
}
else
{
// TODO: handle rich text
}
}
else
{
// TODO: We draw comb
}
}
void PDFXFAEngineImpl::drawItemBorder(const xfa::XFA_border* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
@ -11430,6 +11699,59 @@ void PDFXFAEngineImpl::drawItemFill(const xfa::XFA_fill* item,
}
}
void PDFXFAEngineImpl::drawItemLine(const xfa::XFA_line* item,
QList<PDFRenderError>& errors,
QRectF nominalContentArea,
QPainter* painter)
{
if (!item)
{
return;
}
QPen pen = createPenFromEdge(item->getEdge(), errors);
if (pen.style() == Qt::NoPen)
{
return;
}
QLineF line;
switch (item->getSlope())
{
case pdf::xfa::XFA_BaseNode::SLOPE::Backslash:
line = QLineF(nominalContentArea.topLeft(), nominalContentArea.bottomRight());
break;
case pdf::xfa::XFA_BaseNode::SLOPE::Slash:
line = QLineF(nominalContentArea.bottomLeft(), nominalContentArea.topRight());
break;
}
PDFReal offset = 0.0;
switch (item->getHand())
{
case xfa::XFA_BaseNode::HAND::Even:
break;
case xfa::XFA_BaseNode::HAND::Left:
offset = -pen.widthF() * 0.5;
break;
case xfa::XFA_BaseNode::HAND::Right:
offset = +pen.widthF() * 0.5;
break;
}
if (!qFuzzyIsNull(offset))
{
const QLineF unitVector = line.normalVector().unitVector();
const qreal offsetX = unitVector.dx() * offset;
const qreal offsetY = unitVector.dy() * offset;
line.translate(offsetX, offsetY);
}
painter->setPen(std::move(pen));
painter->drawLine(line);
}
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,