From 9222a600390822d3fd94dc7167277ca03f934e1f Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 2 Jun 2024 16:57:47 +0200 Subject: [PATCH] Editor plugin: Edit text --- .../EditorPlugin/editorplugin.cpp | 15 +- .../EditorPlugin/editorplugin.h | 1 + .../sources/pdfpagecontenteditorprocessor.cpp | 426 ++++++++++-------- .../sources/pdfpagecontenteditorprocessor.h | 6 +- 4 files changed, 250 insertions(+), 198 deletions(-) diff --git a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp index 6f8b257..a05a680 100644 --- a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp +++ b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp @@ -151,7 +151,7 @@ void EditorPlugin::setWidget(pdf::PDFWidget* widget) connect(&m_scene, &pdf::PDFPageContentScene::selectionChanged, this, &EditorPlugin::onSceneSelectionChanged); connect(&m_scene, &pdf::PDFPageContentScene::editElementRequest, this, &EditorPlugin::onSceneEditElement); connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); - connect(activateAction, &QAction::triggered, this, &EditorPlugin::setActive); + connect(activateAction, &QAction::triggered, this, &EditorPlugin::onSetActive); connect(m_widget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::drawSpaceChanged, this, &EditorPlugin::onDrawSpaceChanged); updateActions(); @@ -200,7 +200,7 @@ bool EditorPlugin::save() const pdf::PDFPage* page = m_document->getCatalog()->getPage(pageIndex); const pdf::PDFEditedPageContent& editedPageContent = m_editedPageContent.at(pageIndex); - pdf::PDFPageContentEditorContentStreamBuilder contentStreamBuilder; + pdf::PDFPageContentEditorContentStreamBuilder contentStreamBuilder(m_document); contentStreamBuilder.setFontDictionary(editedPageContent.getFontDictionary()); auto it = elementsByPage.find(pageIndex); @@ -477,6 +477,17 @@ void EditorPlugin::setActive(bool active) } } +void EditorPlugin::onSetActive(bool active) +{ + if (!active && !save()) + { + updateActions(); + return; + } + + setActive(active); +} + void EditorPlugin::updateActions() { m_actions[Activate]->setEnabled(m_document); diff --git a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h index 76e6d10..f1da0b8 100644 --- a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h +++ b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h @@ -103,6 +103,7 @@ private: }; void setActive(bool active); + void onSetActive(bool active); void updateActions(); void updateGraphics(); diff --git a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp index b3342bd..786ec74 100644 --- a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp +++ b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp @@ -778,7 +778,8 @@ void PDFEditedPageContentElementText::setItemsAsText(const QString& newItemsAsTe m_itemsAsText = newItemsAsText; } -PDFPageContentEditorContentStreamBuilder::PDFPageContentEditorContentStreamBuilder() +PDFPageContentEditorContentStreamBuilder::PDFPageContentEditorContentStreamBuilder(PDFDocument* document) : + m_document(document) { } @@ -1147,231 +1148,266 @@ void PDFPageContentEditorContentStreamBuilder::writeText(QTextStream& stream, co { stream << "q BT" << Qt::endl; - QXmlStreamReader reader(text); - m_textFont = m_currentState.getTextFont(); + QString xml = QString("%1").arg(text); - auto isCommand = [&reader](const char* tag) -> bool - { - QString tagString = reader.name().toString(); - QXmlStreamAttributes attributes = reader.attributes(); - return tagString == "tr" && attributes.size() == 1 && attributes.hasAttribute("v"); - }; + QXmlStreamReader reader(xml); + m_textFont = m_currentState.getTextFont(); while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); - if (reader.isStartElement()) + switch (reader.tokenType()) { - QXmlStreamAttributes attributes = reader.attributes(); + case QXmlStreamReader::NoToken: + break; - if (isCommand("tr")) - { - const QXmlStreamAttribute& attribute = attributes.front(); - bool ok = false; - const int textRenderingMode = attribute.value().toInt(&ok); - if (!ok || textRenderingMode < 0 || textRenderingMode > 7) - { - addError(PDFTranslationContext::tr("Invalid rendering mode '%1'. Valid values are 0-7.").arg(textRenderingMode)); - } - else - { - stream << textRenderingMode << " Tr" << Qt::endl; - } - } - else if (isCommand("ts")) - { - const QXmlStreamAttribute& attribute = attributes.front(); - bool ok = false; - const double textRise = attribute.value().toDouble(&ok); + case QXmlStreamReader::Invalid: + addError(PDFTranslationContext::tr("Invalid XML text.")); + break; - if (!ok) - { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); - } - else - { - stream << textRise << " Ts" << Qt::endl; - } - } - else if (isCommand("tc")) - { - const QXmlStreamAttribute& attribute = attributes.front(); - bool ok = false; - const double textCharacterSpacing = attribute.value().toDouble(&ok); + case QXmlStreamReader::StartDocument: + case QXmlStreamReader::EndDocument: + case QXmlStreamReader::EndElement: + case QXmlStreamReader::Comment: + case QXmlStreamReader::DTD: + case QXmlStreamReader::ProcessingInstruction: + case QXmlStreamReader::EntityReference: + break; - if (!ok) - { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); - } - else - { - stream << textCharacterSpacing << " Tc" << Qt::endl; - } - } - else if (isCommand("tw")) - { - const QXmlStreamAttribute& attribute = attributes.front(); - bool ok = false; - const double textWordSpacing = attribute.value().toDouble(&ok); + case QXmlStreamReader::StartElement: + writeTextCommand(stream, reader); + break; - if (!ok) - { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); - } - else - { - stream << textWordSpacing << " Tw" << Qt::endl; - } - } - else if (isCommand("tl")) + case QXmlStreamReader::Characters: { - const QXmlStreamAttribute& attribute = attributes.front(); - bool ok = false; - const double textLeading = attribute.value().toDouble(&ok); + QString characters = reader.text().toString(); - if (!ok) + if (m_textFont) { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); - } - else - { - stream << textLeading << " TL" << Qt::endl; - } - } - else if (isCommand("tz")) - { - const QXmlStreamAttribute& attribute = attributes.front(); - bool ok = false; - const PDFReal textScaling = attribute.value().toDouble(&ok); + PDFEncodedText encodedText = m_textFont->encodeText(characters); - if (!ok) - { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); - } - else - { - stream << textScaling << " Tz" << Qt::endl; - } - } - else if (reader.name().toString() == "tf") - { - if (attributes.hasAttribute("font") && attributes.hasAttribute("size")) - { - bool ok = false; - QByteArray v1 = attributes.value("font").toString().toLatin1(); - PDFReal v2 = attributes.value("size").toDouble(&ok); - - if (!ok) + if (!encodedText.encodedText.isEmpty()) { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attributes.value("size").toString())); + stream << "<" << encodedText.encodedText.toHex() << "> Tj" << Qt::endl; } - else + + if (!encodedText.isValid) { - v1 = selectFont(v1); - stream << "/" << v1 << " " << v2 << " Tf" << Qt::endl; + addError(PDFTranslationContext::tr("Error during converting text to font encoding. Some characters were not converted: '%1'.").arg(encodedText.errorString)); } } else { - addError(PDFTranslationContext::tr("Text font command requires two attributes - font and size.")); + addError(PDFTranslationContext::tr("Text font not defined!")); } + break; } - else if (reader.name().toString() == "tpos") - { - if (attributes.hasAttribute("x") && attributes.hasAttribute("y")) - { - bool ok1 = false; - bool ok2 = false; - PDFReal v1 = attributes.value("x").toDouble(&ok1); - PDFReal v2 = attributes.value("y").toDouble(&ok2); - if (!ok1) - { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attributes.value("x").toString())); - } - else if (!ok2) - { - addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attributes.value("y").toString())); - } - else - { - stream << v1 << " " << v2 << " Td" << Qt::endl; - } - } - else - { - addError(PDFTranslationContext::tr("Text translation command requires two attributes - x and y.")); - } - } - else if (reader.name().toString() == "tmatrix") - { - if (attributes.hasAttribute("m11") && attributes.hasAttribute("m12") && - attributes.hasAttribute("m21") && attributes.hasAttribute("m22") && - attributes.hasAttribute("x") && attributes.hasAttribute("y")) - { - bool ok1 = false; - bool ok2 = false; - bool ok3 = false; - bool ok4 = false; - bool ok5 = false; - bool ok6 = false; - PDFReal m11 = attributes.value("m11").toDouble(&ok1); - PDFReal m12 = attributes.value("m12").toDouble(&ok2); - PDFReal m21 = attributes.value("m21").toDouble(&ok3); - PDFReal m22 = attributes.value("m22").toDouble(&ok4); - PDFReal x = attributes.value("x").toDouble(&ok5); - PDFReal y = attributes.value("y").toDouble(&ok6); - - if (!ok1 || !ok2 || !ok3 || !ok4 || !ok5 | !ok6) - { - addError(PDFTranslationContext::tr("Invalid text matrix parameters.")); - } - - else - { - stream << m11 << " " << m12 << " " << m21 << " " << m22 << " " << x << " " << y << " Tm" << Qt::endl; - } - } - else - { - addError(PDFTranslationContext::tr("Set text matrix command requires six elements - m11, m12, m21, m22, x, y.")); - } - } - else - { - addError(PDFTranslationContext::tr("Invalid command '%1'.").arg(reader.name().toString())); - } - } - - if (reader.isCharacters()) - { - QString characters = reader.text().toString(); - - if (m_textFont) - { - PDFEncodedText encodedText = m_textFont->encodeText(characters); - - if (!encodedText.encodedText.isEmpty()) - { - stream << "<" << encodedText.encodedText.toHex() << "> Tj" << Qt::endl; - } - - if (!encodedText.isValid) - { - addError(PDFTranslationContext::tr("Error during converting text to font encoding. Some characters were not converted: '%1'.").arg(encodedText.errorString)); - } - } - else - { - addError(PDFTranslationContext::tr("Text font not defined!")); - } + default: + Q_ASSERT(false); + break; } } stream << "ET Q" << Qt::endl; } +void PDFPageContentEditorContentStreamBuilder::writeTextCommand(QTextStream& stream, const QXmlStreamReader& reader) +{ + QXmlStreamAttributes attributes = reader.attributes(); + + auto isCommand = [&reader](const char* tag) -> bool + { + QString tagString = reader.name().toString(); + QXmlStreamAttributes attributes = reader.attributes(); + return tagString == QLatin1String(tag) && attributes.size() == 1 && attributes.hasAttribute("v"); + }; + + if (reader.name().toString() == "doc") + { + return; + } + + if (isCommand("tr")) + { + const QXmlStreamAttribute& attribute = attributes.front(); + bool ok = false; + const int textRenderingMode = attribute.value().toInt(&ok); + if (!ok || textRenderingMode < 0 || textRenderingMode > 7) + { + addError(PDFTranslationContext::tr("Invalid rendering mode '%1'. Valid values are 0-7.").arg(textRenderingMode)); + } + else + { + stream << textRenderingMode << " Tr" << Qt::endl; + } + } + else if (isCommand("ts")) + { + const QXmlStreamAttribute& attribute = attributes.front(); + bool ok = false; + const double textRise = attribute.value().toDouble(&ok); + + if (!ok) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); + } + else + { + stream << textRise << " Ts" << Qt::endl; + } + } + else if (isCommand("tc")) + { + const QXmlStreamAttribute& attribute = attributes.front(); + bool ok = false; + const double textCharacterSpacing = attribute.value().toDouble(&ok); + + if (!ok) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); + } + else + { + stream << textCharacterSpacing << " Tc" << Qt::endl; + } + } + else if (isCommand("tw")) + { + const QXmlStreamAttribute& attribute = attributes.front(); + bool ok = false; + const double textWordSpacing = attribute.value().toDouble(&ok); + + if (!ok) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); + } + else + { + stream << textWordSpacing << " Tw" << Qt::endl; + } + } + else if (isCommand("tl")) + { + const QXmlStreamAttribute& attribute = attributes.front(); + bool ok = false; + const double textLeading = attribute.value().toDouble(&ok); + + if (!ok) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); + } + else + { + stream << textLeading << " TL" << Qt::endl; + } + } + else if (isCommand("tz")) + { + const QXmlStreamAttribute& attribute = attributes.front(); + bool ok = false; + const PDFReal textScaling = attribute.value().toDouble(&ok); + + if (!ok) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attribute.value().toString())); + } + else + { + stream << textScaling << " Tz" << Qt::endl; + } + } + else if (reader.name().toString() == "tf") + { + if (attributes.hasAttribute("font") && attributes.hasAttribute("size")) + { + bool ok = false; + QByteArray v1 = attributes.value("font").toString().toLatin1(); + PDFReal v2 = attributes.value("size").toDouble(&ok); + + if (!ok) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attributes.value("size").toString())); + } + else + { + v1 = selectFont(v1); + stream << "/" << v1 << " " << v2 << " Tf" << Qt::endl; + } + } + else + { + addError(PDFTranslationContext::tr("Text font command requires two attributes - font and size.")); + } + } + else if (reader.name().toString() == "tpos") + { + if (attributes.hasAttribute("x") && attributes.hasAttribute("y")) + { + bool ok1 = false; + bool ok2 = false; + PDFReal v1 = attributes.value("x").toDouble(&ok1); + PDFReal v2 = attributes.value("y").toDouble(&ok2); + + if (!ok1) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attributes.value("x").toString())); + } + else if (!ok2) + { + addError(PDFTranslationContext::tr("Cannot convert text '%1' to number.").arg(attributes.value("y").toString())); + } + else + { + stream << v1 << " " << v2 << " Td" << Qt::endl; + } + } + else + { + addError(PDFTranslationContext::tr("Text translation command requires two attributes - x and y.")); + } + } + else if (reader.name().toString() == "tmatrix") + { + if (attributes.hasAttribute("m11") && attributes.hasAttribute("m12") && + attributes.hasAttribute("m21") && attributes.hasAttribute("m22") && + attributes.hasAttribute("x") && attributes.hasAttribute("y")) + { + bool ok1 = false; + bool ok2 = false; + bool ok3 = false; + bool ok4 = false; + bool ok5 = false; + bool ok6 = false; + PDFReal m11 = attributes.value("m11").toDouble(&ok1); + PDFReal m12 = attributes.value("m12").toDouble(&ok2); + PDFReal m21 = attributes.value("m21").toDouble(&ok3); + PDFReal m22 = attributes.value("m22").toDouble(&ok4); + PDFReal x = attributes.value("x").toDouble(&ok5); + PDFReal y = attributes.value("y").toDouble(&ok6); + + if (!ok1 || !ok2 || !ok3 || !ok4 || !ok5 | !ok6) + { + addError(PDFTranslationContext::tr("Invalid text matrix parameters.")); + } + + else + { + stream << m11 << " " << m12 << " " << m21 << " " << m22 << " " << x << " " << y << " Tm" << Qt::endl; + } + } + else + { + addError(PDFTranslationContext::tr("Set text matrix command requires six elements - m11, m12, m21, m22, x, y.")); + } + } + else + { + addError(PDFTranslationContext::tr("Invalid command '%1'.").arg(reader.name().toString())); + } +} + void PDFPageContentEditorContentStreamBuilder::writeImage(QTextStream& stream, const QImage& image) { QByteArray key; diff --git a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h index 50fbb9a..24a722e 100644 --- a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h +++ b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h @@ -22,6 +22,8 @@ #include +class QXmlStreamReader; + namespace pdf { @@ -211,7 +213,7 @@ private: class PDF4QTLIBCORESHARED_EXPORT PDFPageContentEditorContentStreamBuilder { public: - PDFPageContentEditorContentStreamBuilder(); + PDFPageContentEditorContentStreamBuilder(PDFDocument* document); void writeStateDifference(QTextStream& stream, const PDFPageContentProcessorState& state); void writeElement(const PDFEditedPageContentElement* element); @@ -232,7 +234,9 @@ private: const QPainterPath& path, bool isStroking, bool isFilling); + void writeText(QTextStream& stream, const QString& text); + void writeTextCommand(QTextStream& stream, const QXmlStreamReader& reader); void writeImage(QTextStream& stream, const QImage& image);