From bc6ca3fc460b7909695b1043df934b2f927b6ac8 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 21 Mar 2020 16:36:27 +0100 Subject: [PATCH] Generating code --- CodeGenerator/codegenerator.cpp | 422 +++++++++++++++++++++ CodeGenerator/codegenerator.h | 40 ++ CodeGenerator/generatormainwindow.cpp | 32 ++ CodeGenerator/generatormainwindow.h | 5 + CodeGenerator/generatormainwindow.ui | 27 ++ PdfForQtLib/sources/pdfdocument.cpp | 5 + PdfForQtLib/sources/pdfdocument.h | 5 + PdfForQtLib/sources/pdfdocumentbuilder.cpp | 180 +++++++++ PdfForQtLib/sources/pdfdocumentbuilder.h | 75 +++- PdfForQtLib/sources/pdfencoding.cpp | 36 ++ PdfForQtLib/sources/pdfencoding.h | 12 + PdfForQtLib/sources/pdfobject.cpp | 79 ++++ PdfForQtLib/sources/pdfobject.h | 44 +++ PdfForQtLib/sources/pdfsecurityhandler.cpp | 1 - generated_code_definition.xml | 12 +- 15 files changed, 966 insertions(+), 9 deletions(-) diff --git a/CodeGenerator/codegenerator.cpp b/CodeGenerator/codegenerator.cpp index 72cb354..f499a58 100644 --- a/CodeGenerator/codegenerator.cpp +++ b/CodeGenerator/codegenerator.cpp @@ -17,6 +17,12 @@ #include "codegenerator.h" +#include +#include +#include +#include +#include + namespace codegen { @@ -57,6 +63,18 @@ void GeneratedCodeStorage::removeFunction(GeneratedFunction* function) m_functions.removeOne(function); } +void GeneratedCodeStorage::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const +{ + stream << Qt::endl << Qt::endl; + + for (const QObject* object : m_functions) + { + const GeneratedFunction* generatedFunction = qobject_cast(object); + generatedFunction->generateCode(stream, parameters); + stream << Qt::endl << Qt::endl; + } +} + QObject* Serializer::load(const QDomElement& element, QObject* parent) { QString className = element.attribute("class"); @@ -223,6 +241,95 @@ void CodeGenerator::store(QDomDocument& document) } } +void CodeGenerator::generateCode(QString headerName, QString sourceName) const +{ + QString startMark = "/* START GENERATED CODE */"; + QString endMark = "/* END GENERATED CODE */"; + QString className = "PDFDocumentBuilder"; + const int indent = 4; + + QFile headerFile(headerName); + if (headerFile.exists()) + { + if (headerFile.open(QFile::ReadOnly)) + { + QString utfCode = QString::fromUtf8(headerFile.readAll()); + headerFile.close(); + + int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); + int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); + + QString frontPart = utfCode.left(startIndex); + QString backPart = utfCode.mid(endIndex); + QString headerGeneratedCode = generateHeader(indent); + QString allCode = frontPart + headerGeneratedCode + backPart; + + headerFile.open(QFile::WriteOnly | QFile::Truncate); + headerFile.write(allCode.toUtf8()); + headerFile.close(); + } + } + + QFile sourceFile(sourceName); + if (sourceFile.exists()) + { + if (sourceFile.open(QFile::ReadOnly)) + { + QString utfCode = QString::fromUtf8(sourceFile.readAll()); + sourceFile.close(); + + int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); + int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); + + QString frontPart = utfCode.left(startIndex); + QString backPart = utfCode.mid(endIndex); + QString sourceGeneratedCode = generateSource(className, indent); + QString allCode = frontPart + sourceGeneratedCode + backPart; + + sourceFile.open(QFile::WriteOnly | QFile::Truncate); + sourceFile.write(allCode.toUtf8()); + sourceFile.close(); + } + } +} + +QString CodeGenerator::generateHeader(int indent) const +{ + QByteArray ba; + { + QTextStream stream(&ba, QIODevice::WriteOnly); + stream.setCodec("UTF-8"); + stream.setRealNumberPrecision(3); + stream.setRealNumberNotation(QTextStream::FixedNotation); + + CodeGeneratorParameters parameters; + parameters.header = true; + parameters.indent = indent; + m_storage->generateCode(stream, parameters); + } + + return QString::fromUtf8(ba); +} + +QString CodeGenerator::generateSource(QString className, int indent) const +{ + QByteArray ba; + { + QTextStream stream(&ba, QIODevice::WriteOnly); + stream.setCodec("UTF-8"); + stream.setRealNumberPrecision(3); + stream.setRealNumberNotation(QTextStream::FixedNotation); + + CodeGeneratorParameters parameters; + parameters.header = false; + parameters.indent = indent; + parameters.className = className; + m_storage->generateCode(stream, parameters); + } + + return QString::fromUtf8(ba); +} + GeneratedFunction::GeneratedFunction(QObject* parent) : BaseClass(parent) { @@ -274,6 +381,91 @@ GeneratedFunction* GeneratedFunction::clone(QObject* parent) return qobject_cast(Serializer::clone(this, parent)); } +void GeneratedFunction::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const +{ + QStringList parameterCaptions; + QStringList parameterTexts; + std::function gatherParameters = [&](const GeneratedBase* object, Pass pass) + { + if (pass != Pass::Enter) + { + return; + } + + if (const GeneratedParameter* generatedParameter = qobject_cast(object)) + { + parameterCaptions << QString("%1 %2").arg(generatedParameter->getParameterName(), generatedParameter->getParameterDescription()); + parameterTexts << QString("%1 %2").arg(getCppType(generatedParameter->getParameterDataType()), generatedParameter->getParameterName()); + } + }; + applyFunctor(gatherParameters); + + if (parameters.header) + { + // Generate header source code + + // Function comments + for (const QString& string : getFormattedTextWithLayout("/// ", "/// ", getFunctionDescription(), parameters.indent)) + { + stream << string << Qt::endl; + } + + // Function parameter comments + for (const QString& parameterCaption : parameterCaptions) + { + for (const QString& string : getFormattedTextWithLayout("/// \\param ", "/// ", parameterCaption, parameters.indent)) + { + stream << string << Qt::endl; + } + } + + // Function declaration + QString functionHeader = QString("%1 %2(").arg(getCppType(getReturnType()), getFunctionName()); + QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); + QString functionFooter = QString(");"); + + for (QString& str : parameterTexts) + { + str += ","; + } + parameterTexts.back().replace(",", functionFooter); + + QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, parameters.indent); + for (const QString& functionDeclarationItem : functionDeclaration) + { + stream << functionDeclarationItem << Qt::endl; + } + } + else + { + // Generate c++ source code + QString functionHeader = QString("%1 %2::%3(").arg(getCppType(getReturnType()), parameters.className, getFunctionName()); + QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); + QString functionFooter = QString(")"); + + for (QString& str : parameterTexts) + { + str += ","; + } + parameterTexts.back().replace(",", functionFooter); + + QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, 0); + for (const QString& functionDeclarationItem : functionDeclaration) + { + stream << functionDeclarationItem << Qt::endl; + } + + QString indent(parameters.indent, QChar(QChar::Space)); + + stream << "{" << Qt::endl; + stream << indent << "PDFObjectFactory objectBuilder;" << Qt::endl << Qt::endl; + + generateSourceCode(stream, parameters); + + stream << "}" << Qt::endl; + } +} + bool GeneratedFunction::hasField(GeneratedBase::FieldType fieldType) const { switch (fieldType) @@ -552,6 +744,86 @@ void GeneratedAction::setCode(const QString& code) m_code = code; } +void GeneratedAction::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const +{ + if (pass == Pass::Enter && getActionType() == Code) + { + QString indent(parameters.indent, QChar(QChar::Space)); + QStringList lines = getCode().split(QChar('\n'), Qt::KeepEmptyParts, Qt::CaseInsensitive); + + for (QString string : lines) + { + string = string.trimmed(); + if (!string.isEmpty()) + { + stream << indent << string << Qt::endl; + } + else + { + stream << Qt::endl; + } + } + } + + if (pass == Pass::Leave && getActionType() == CreateObject) + { + QString indent(parameters.indent, QChar(QChar::Space)); + + switch (getVariableType()) + { + case _PDFObject: + { + stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = objectBuilder.takeObject();"; + break; + } + + case _PDFObjectReference: + { + stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = addObject(objectBuilder.takeObject());"; + break; + } + + default: + Q_ASSERT(false); + break; + } + } + + if (pass == Pass::Leave && getActionType() != Parameters && !parameters.isLastItem) + { + stream << Qt::endl; + } +} + +void GeneratedBase::generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const +{ + generateSourceCodeImpl(stream, parameters, Pass::Enter); + + for (const QObject* object : m_items) + { + const GeneratedBase* generatedBase = qobject_cast(object); + CodeGeneratorParameters parametersTemporary = parameters; + parametersTemporary.isFirstItem = object == m_items.front(); + parametersTemporary.isLastItem = object == m_items.back(); + generatedBase->generateSourceCode(stream, parametersTemporary); + } + + generateSourceCodeImpl(stream, parameters, Pass::Leave); +} + +void GeneratedBase::applyFunctor(std::function& functor) const +{ + functor(this, Pass::Enter); + + for (const QObject* object : m_items) + { + const GeneratedBase* generatedBase = qobject_cast(object); + generatedBase->applyFunctor(functor); + } + + functor(this, Pass::Leave); +} + bool GeneratedBase::canPerformOperation(Operation operation) const { const bool isFunction = qobject_cast(this) != nullptr; @@ -679,6 +951,73 @@ void GeneratedBase::clearItems() m_items.clear(); } +void GeneratedBase::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const +{ + Q_UNUSED(stream); + Q_UNUSED(parameters); + Q_UNUSED(pass); +} + +QString GeneratedBase::getCppType(DataType type) const +{ + return Serializer::convertEnumToString(type).mid(1); +} + +QStringList GeneratedBase::getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const +{ + QFont font = QApplication::font(); + QFontMetrics fontMetrics(font); + + int usedLength = indent + qMax(firstPrefix.length(), prefix.length()); + int length = 80 - usedLength; + QString testText(length, QChar('A')); + int width = fontMetrics.width(testText); + + QTextLayout layout(text, font); + layout.setCacheEnabled(false); + QTextOption textOption = layout.textOption(); + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + layout.setTextOption(textOption); + + layout.beginLayout(); + while (true) + { + QTextLine textLine = layout.createLine(); + if (!textLine.isValid()) + { + break; + } + + textLine.setLineWidth(width); + } + layout.endLayout(); + + QStringList texts; + const int lineCount = layout.lineCount(); + for (int i = 0; i < lineCount; ++i) + { + QTextLine line = layout.lineAt(i); + texts << text.mid(line.textStart(), line.textLength()); + } + return getFormattedTextBlock(firstPrefix, prefix, texts, indent); +} + +QStringList GeneratedBase::getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const +{ + QString indentText(indent, QChar(QChar::Space)); + firstPrefix.prepend(indentText); + prefix.prepend(indentText); + + QStringList result; + QString currentPrefix = firstPrefix; + for (const QString& text : texts) + { + result << QString("%1%2").arg(currentPrefix, text); + currentPrefix = prefix; + } + return result; +} + GeneratedPDFObject::GeneratedPDFObject(QObject* parent) : BaseClass(parent) { @@ -844,6 +1183,89 @@ void GeneratedPDFObject::setDictionaryItemName(const QString& dictionaryItemName m_dictionaryItemName = dictionaryItemName; } +void GeneratedPDFObject::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const +{ + QString indent(parameters.indent, QChar(QChar::Space)); + QString writeTo("objectBuilder << "); + + switch (getObjectType()) + { + case codegen::GeneratedPDFObject::Object: + { + if (pass == Pass::Enter) + { + stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::ArraySimple: + { + if (pass == Pass::Enter) + { + stream << indent << "objectBuilder.beginArray();" << Qt::endl; + stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; + stream << indent << "objectBuilder.endArray();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::ArrayComplex: + { + if (pass == Pass::Enter) + { + stream << indent << "objectBuilder.beginArray();" << Qt::endl; + } + if (pass == Pass::Leave) + { + stream << indent << "objectBuilder.endArray();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::Dictionary: + { + if (pass == Pass::Enter) + { + stream << indent << "objectBuilder.beginDictionary();" << Qt::endl; + } + if (pass == Pass::Leave) + { + stream << indent << "objectBuilder.endDictionary();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::DictionaryItemSimple: + { + if (pass == Pass::Enter) + { + stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; + stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; + stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::DictionaryItemComplex: + { + if (pass == Pass::Enter) + { + stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; + } + if (pass == Pass::Leave) + { + stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; + } + break; + } + + default: + Q_ASSERT(false); + break; + } +} + GeneratedParameter::GeneratedParameter(QObject* parent) : BaseClass(parent) { diff --git a/CodeGenerator/codegenerator.h b/CodeGenerator/codegenerator.h index c9ee168..a8c8383 100644 --- a/CodeGenerator/codegenerator.h +++ b/CodeGenerator/codegenerator.h @@ -29,6 +29,15 @@ namespace codegen { +struct CodeGeneratorParameters +{ + bool header = false; + int indent = 0; + QString className; + bool isFirstItem = false; + bool isLastItem = false; +}; + class Serializer { public: @@ -97,6 +106,8 @@ public: GeneratedFunction* addFunction(GeneratedFunction* function); void removeFunction(GeneratedFunction* function); + void generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; + private: QObjectList m_functions; }; @@ -147,6 +158,15 @@ public: virtual QStringList getCaptions() const = 0; virtual GeneratedBase* appendItem() = 0; + enum class Pass + { + Enter, + Leave + }; + + void generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; + void applyFunctor(std::function& functor) const; + enum class Operation { Delete, @@ -167,6 +187,13 @@ public: void removeItem(QObject* object); void clearItems(); +protected: + virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const; + + QString getCppType(DataType type) const; + QStringList getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const; + QStringList getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const; + private: QObjectList m_items; }; @@ -249,6 +276,9 @@ public: QString getDictionaryItemName() const; void setDictionaryItemName(const QString& dictionaryItemName); +protected: + virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const override; + private: QString m_dictionaryItemName; ObjectType m_objectType = Object; @@ -298,6 +328,9 @@ public: QString getCode() const; void setCode(const QString& code); +protected: + virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const override; + private: ActionType m_actionType; QString m_variableName; @@ -355,6 +388,8 @@ public: /// Create a clone of this function GeneratedFunction* clone(QObject* parent); + void generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; + private: FunctionType m_functionType = FunctionType::Annotations; QString m_functionName; @@ -382,7 +417,12 @@ public: void load(const QDomDocument& document); void store(QDomDocument& document); + void generateCode(QString headerName, QString sourceName) const; + private: + QString generateHeader(int indent) const; + QString generateSource(QString className, int indent) const; + GeneratedCodeStorage* m_storage = nullptr; }; diff --git a/CodeGenerator/generatormainwindow.cpp b/CodeGenerator/generatormainwindow.cpp index dfbfee3..3d3bed1 100644 --- a/CodeGenerator/generatormainwindow.cpp +++ b/CodeGenerator/generatormainwindow.cpp @@ -83,6 +83,8 @@ void GeneratorMainWindow::saveSettings() { QSettings settings("MelkaJ"); settings.setValue("fileName", m_defaultFileName); + settings.setValue("headerFile", m_headerFileName); + settings.setValue("sourceFile", m_sourceFileName); } void GeneratorMainWindow::loadGeneratedSettings() @@ -349,6 +351,8 @@ void GeneratorMainWindow::loadSettings() { QSettings settings("MelkaJ"); m_defaultFileName = settings.value("fileName").toString(); + m_headerFileName = settings.value("headerFile", QVariant()).toString(); + m_sourceFileName = settings.value("sourceFile", QVariant()).toString(); } void GeneratorMainWindow::save(const QString& fileName) @@ -486,3 +490,31 @@ void GeneratorMainWindow::on_itemNewSiblingButton_clicked() loadGeneratedSettings(); } } + +void GeneratorMainWindow::on_actionSet_code_header_h_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp header"), QString(), "cpp header (*.h)"); + if (!fileName.isEmpty()) + { + m_headerFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionSet_code_source_cpp_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp source"), QString(), "cpp source (*.cpp)"); + if (!fileName.isEmpty()) + { + m_sourceFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionGenerate_code_triggered() +{ + if (m_generator) + { + m_generator->generateCode(m_headerFileName, m_sourceFileName); + } +} diff --git a/CodeGenerator/generatormainwindow.h b/CodeGenerator/generatormainwindow.h index 299c811..4ece077 100644 --- a/CodeGenerator/generatormainwindow.h +++ b/CodeGenerator/generatormainwindow.h @@ -77,6 +77,9 @@ private slots: void on_itemDownButton_clicked(); void on_itemNewChildButton_clicked(); void on_itemNewSiblingButton_clicked(); + void on_actionSet_code_header_h_triggered(); + void on_actionSet_code_source_cpp_triggered(); + void on_actionGenerate_code_triggered(); private: void loadSettings(); @@ -97,6 +100,8 @@ private: codegen::GeneratedFunction* m_currentFunction; codegen::GeneratedBase* m_currentSettings; QString m_defaultFileName; + QString m_headerFileName; + QString m_sourceFileName; std::map m_mapFunctionToWidgetItem; bool m_isLoadingData; }; diff --git a/CodeGenerator/generatormainwindow.ui b/CodeGenerator/generatormainwindow.ui index b2d8fba..a0eb644 100644 --- a/CodeGenerator/generatormainwindow.ui +++ b/CodeGenerator/generatormainwindow.ui @@ -233,7 +233,16 @@ + + + Code + + + + + + @@ -257,6 +266,24 @@ Save As... + + + Set code header (*.h) + + + + + Set code source (*.cpp) + + + + + Generate code + + + Ctrl+G + + diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index 3da7289..7744716 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -232,6 +232,11 @@ PDFObjectReference PDFObjectStorage::addObject(PDFObject object) return reference; } +void PDFObjectStorage::setObject(PDFObjectReference reference, PDFObject object) +{ + m_objects[reference.objectNumber] = Entry(reference.generation, qMove(object)); +} + QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object) { const PDFObject& dereferencedObject = m_document->getObject(object); diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 4f561a6..e1fe7ea 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -84,6 +84,11 @@ public: /// \returns Reference to new object PDFObjectReference addObject(PDFObject object); + /// Sets object to object storage. Reference must exist. + /// \param reference Reference to object + /// \param object New value of object + void setObject(PDFObjectReference reference, PDFObject object); + private: PDFObjects m_objects; PDFObject m_trailerDictionary; diff --git a/PdfForQtLib/sources/pdfdocumentbuilder.cpp b/PdfForQtLib/sources/pdfdocumentbuilder.cpp index af9cef6..407be28 100644 --- a/PdfForQtLib/sources/pdfdocumentbuilder.cpp +++ b/PdfForQtLib/sources/pdfdocumentbuilder.cpp @@ -16,6 +16,7 @@ // along with PDFForQt. If not, see . #include "pdfdocumentbuilder.h" +#include "pdfencoding.h" namespace pdf { @@ -62,6 +63,82 @@ void PDFObjectFactory::endDictionaryItem() std::get(dictionaryItem.object).addEntry(qMove(topItem.itemName), qMove(std::get(topItem.object))); } +PDFObjectFactory& PDFObjectFactory::operator<<(QString textString) +{ + if (!PDFEncoding::canConvertToEncoding(textString, PDFEncoding::Encoding::PDFDoc)) + { + // Use unicode encoding + QByteArray ba; + + { + QTextStream textStream(&ba, QIODevice::WriteOnly); + textStream.setCodec("UTF-16BE"); + textStream.setGenerateByteOrderMark(true); + textStream << textString; + } + + addObject(PDFObject::createString(std::make_shared(qMove(ba)))); + } + else + { + // Use PDF document encoding + addObject(PDFObject::createString(std::make_shared(PDFEncoding::convertToEncoding(textString, PDFEncoding::Encoding::PDFDoc)))); + } + + return *this; +} + +PDFObjectFactory& PDFObjectFactory::operator<<(WrapAnnotationColor color) +{ + if (color.color.isValid()) + { + // Jakub Melka: we will decide, if we have gray/rgb/cmyk color + QColor value = color.color; + if (value.spec() == QColor::Cmyk) + { + *this << std::initializer_list{ value.cyanF(), value.magentaF(), value.yellowF(), value.blackF() }; + } + else if (qIsGray(value.rgb())) + { + *this << std::initializer_list{ value.redF() }; + } + else + { + *this << std::initializer_list{ value.redF(), value.greenF(), value.blueF() }; + } + } + else + { + addObject(PDFObject::createNull()); + } + + return *this; +} + +PDFObjectFactory& PDFObjectFactory::operator<<(WrapCurrentDateTime) +{ + addObject(PDFObject::createString(std::make_shared(PDFEncoding::converDateTimeToString(QDateTime::currentDateTime())))); + return *this; +} + +PDFObjectFactory& PDFObjectFactory::operator<<(const QRectF& value) +{ + *this << std::initializer_list{ value.left(), value.top(), value.right(), value.bottom() }; + return *this; +} + +PDFObjectFactory& PDFObjectFactory::operator<<(int value) +{ + *this << PDFInteger(value); + return *this; +} + +PDFObjectFactory& PDFObjectFactory::operator<<(WrapName wrapName) +{ + addObject(PDFObject::createName(std::make_shared(qMove(wrapName.name)))); + return *this; +} + PDFObject PDFObjectFactory::takeObject() { Q_ASSERT(m_items.size() == 1); @@ -154,8 +231,111 @@ PDFDocument PDFDocumentBuilder::build() const return PDFDocument(PDFObjectStorage(m_storage), m_version); } +PDFObjectReference PDFDocumentBuilder::addObject(PDFObject object) +{ + return m_storage.addObject(PDFObjectManipulator::removeNullObjects(object)); +} + +void PDFDocumentBuilder::mergeTo(PDFObjectReference reference, PDFObject object) +{ + m_storage.setObject(reference, PDFObjectManipulator::merge(m_storage.getObject(reference), qMove(object), PDFObjectManipulator::RemoveNullObjects)); +} + +QRectF PDFDocumentBuilder::getPopupWindowRect(const QRectF& rectangle) const +{ + return rectangle.translated(rectangle.width() * 1.25, 0); +} + /* START GENERATED CODE */ +PDFObjectReference PDFDocumentBuilder::createAnnotationSquare(PDFObjectReference page, + QRectF rectangle, + PDFReal borderWidth, + QColor fillColor, + QColor strokeColor, + QString title, + QString subject, + QString contents) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Square"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << rectangle; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("F"); + objectBuilder << 4; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Border"); + objectBuilder << std::initializer_list{ 0.0, 0.0, borderWidth }; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("C"); + objectBuilder << WrapAnnotationColor(strokeColor); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("IC"); + objectBuilder << WrapAnnotationColor(fillColor); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << title; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << contents; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subj"); + objectBuilder << subject; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(rectangle), false); + + objectBuilder.beginDictionaryItem("Popup"); + objectBuilder << popupAnnotation; + objectBuilder.endDictionaryItem(); + PDFObject updateAnnotationPopup = objectBuilder.takeObject(); + mergeTo(annotationObject, updateAnnotationPopup); + return PDFObjectReference(); +} + +PDFObjectReference PDFDocumentBuilder::createAnnotationPopup(PDFObjectReference page, + PDFObjectReference parentAnnotation, + QRectF rectangle, + bool opened) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Popup"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << rectangle; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Parent"); + objectBuilder << parentAnnotation; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Open"); + objectBuilder << opened; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference popupAnnotation = addObject(objectBuilder.takeObject()); + return popupAnnotation; +} + /* END GENERATED CODE */ } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdocumentbuilder.h b/PdfForQtLib/sources/pdfdocumentbuilder.h index 8166f57..8bcc931 100644 --- a/PdfForQtLib/sources/pdfdocumentbuilder.h +++ b/PdfForQtLib/sources/pdfdocumentbuilder.h @@ -24,6 +24,30 @@ namespace pdf { +struct WrapName +{ + WrapName(const char* name) : + name(name) + { + + } + + QByteArray name; +}; + +struct WrapAnnotationColor +{ + WrapAnnotationColor(QColor color) : + color(color) + { + + } + + QColor color; +}; + +struct WrapCurrentDateTime { }; + /// Factory for creating various PDF objects, such as simple objects, /// dictionaries, arrays etc. class PDFObjectFactory @@ -45,6 +69,12 @@ public: PDFObjectFactory& operator<<(PDFReal value); PDFObjectFactory& operator<<(PDFInteger value); PDFObjectFactory& operator<<(PDFObjectReference value); + PDFObjectFactory& operator<<(WrapName wrapName); + PDFObjectFactory& operator<<(int value); + PDFObjectFactory& operator<<(const QRectF& value); + PDFObjectFactory& operator<<(WrapCurrentDateTime); + PDFObjectFactory& operator<<(WrapAnnotationColor color); + PDFObjectFactory& operator<<(QString textString); /// Treat containers - write them as array template()))> @@ -119,11 +149,52 @@ public: PDFDocument build() const; - /* START GENERATED CODE */ +/* START GENERATED CODE */ - /* END GENERATED CODE */ + /// Square annotation displays rectangle (or square). When opened, they display pop-up window + /// containing the text of associated note (and window title). Square border/fill color can be defined, + /// along with border width. + /// \param page Page to which is annotation added + /// \param rectangle Area in which is rectangle displayed + /// \param borderWidth Width of the border line of rectangle + /// \param fillColor Fill color of rectangle (interior color). If you do not want to have area color filled, + /// then use invalid QColor. + /// \param strokeColor Stroke color (color of the rectangle border). If you do not want to have a + /// border, then use invalid QColor. + /// \param title Title (it is displayed as title of popup window) + /// \param subject Subject (short description of the subject being adressed by the annotation) + /// \param contents Contents (text displayed, for example, in the marked annotation dialog) + PDFObjectReference createAnnotationSquare(PDFObjectReference page, + QRectF rectangle, + PDFReal borderWidth, + QColor fillColor, + QColor strokeColor, + QString title, + QString subject, + QString contents); + + + /// Creates a new popup annotation on the page. Popup annotation is represented usually by floating + /// window, which can be opened, or closed. Popup annotation is associated with parent annotation, + /// which can be usually markup annotation. Popup annotation displays parent annotation's texts, for + /// example, title, comment, date etc. + /// \param page Page to which is annotation added + /// \param parentAnnotation Parent annotation (for which is popup window displayed) + /// \param rectangle Area on the page, where popup window appears + /// \param opened Is the window opened? + PDFObjectReference createAnnotationPopup(PDFObjectReference page, + PDFObjectReference parentAnnotation, + QRectF rectangle, + bool opened); + + +/* END GENERATED CODE */ private: + PDFObjectReference addObject(PDFObject object); + void mergeTo(PDFObjectReference reference, PDFObject object); + QRectF getPopupWindowRect(const QRectF& rectangle) const; + PDFObjectStorage m_storage; PDFVersion m_version; }; diff --git a/PdfForQtLib/sources/pdfencoding.cpp b/PdfForQtLib/sources/pdfencoding.cpp index eafb2a7..468aef8 100644 --- a/PdfForQtLib/sources/pdfencoding.cpp +++ b/PdfForQtLib/sources/pdfencoding.cpp @@ -2149,6 +2149,7 @@ QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::En if (unicode == (*table)[static_cast(i)]) { converted = i; + break; } } @@ -2158,6 +2159,34 @@ QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::En return result; } +bool PDFEncoding::canConvertToEncoding(const QString& string, PDFEncoding::Encoding encoding) +{ + const encoding::EncodingTable* table = getTableForEncoding(encoding); + Q_ASSERT(table); + + for (QChar character : string) + { + ushort unicode = character.unicode(); + bool converted = false; + + for (int i = 0; i < table->size(); ++i) + { + if (unicode == (*table)[static_cast(i)]) + { + converted = true; + break; + } + } + + if (!converted) + { + return false; + } + } + + return true; +} + QString PDFEncoding::convertTextString(const QByteArray& stream) { if (hasUnicodeLeadMarkings(stream)) @@ -2259,6 +2288,13 @@ QDateTime PDFEncoding::convertToDateTime(const QByteArray& stream) return QDateTime(); } +QByteArray PDFEncoding::converDateTimeToString(QDateTime dateTime) +{ + QDateTime utcDateTime = dateTime.toUTC(); + QString convertedDateTime = QString("D:%1").arg(utcDateTime.toString("yyyyMMddhhmmss")); + return convertedDateTime.toLatin1(); +} + const encoding::EncodingTable* PDFEncoding::getTableForEncoding(Encoding encoding) { switch (encoding) diff --git a/PdfForQtLib/sources/pdfencoding.h b/PdfForQtLib/sources/pdfencoding.h index 82952e2..929c134 100644 --- a/PdfForQtLib/sources/pdfencoding.h +++ b/PdfForQtLib/sources/pdfencoding.h @@ -70,6 +70,13 @@ public: /// \sa convert static QByteArray convertToEncoding(const QString& string, Encoding encoding); + /// Verifies, if string with given unicode characters can be converted using + /// the specified encoding (so, all unicode characters present in the string + /// are also present in given encoding). + /// \param string String to be tested + /// \param encoding Encoding used in verification of conversion + static bool canConvertToEncoding(const QString& string, Encoding encoding); + /// Convert text string to the unicode string, using either PDFDocEncoding, /// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If /// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF @@ -88,6 +95,11 @@ public: /// \param stream Stream, from which date/time is read static QDateTime convertToDateTime(const QByteArray& stream); + /// Convert date/time to string according to PDF Reference 1.7, Chapter 3.8.1. + /// If date is invalid, empty byte array is returned. + /// \param dateTime Date and time to be converted + static QByteArray converDateTimeToString(QDateTime dateTime); + /// Returns conversion table for particular encoding /// \param encoding Encoding static const encoding::EncodingTable* getTableForEncoding(Encoding encoding); diff --git a/PdfForQtLib/sources/pdfobject.cpp b/PdfForQtLib/sources/pdfobject.cpp index dcf2a9a..0700d2a 100644 --- a/PdfForQtLib/sources/pdfobject.cpp +++ b/PdfForQtLib/sources/pdfobject.cpp @@ -204,6 +204,25 @@ const PDFObject& PDFDictionary::get(const char* key) const } } +void PDFDictionary::setEntry(const QByteArray& key, PDFObject&& value) +{ + auto it = find(key); + if (it != m_dictionary.end()) + { + it->second = qMove(value); + } + else + { + addEntry(QByteArray(key), qMove(value)); + } +} + +void PDFDictionary::removeNullObjects() +{ + m_dictionary.erase(std::remove_if(m_dictionary.begin(), m_dictionary.end(), [](const DictionaryEntry& entry) { return entry.second.isNull(); }), m_dictionary.end()); + m_dictionary.shrink_to_fit(); +} + void PDFDictionary::optimize() { m_dictionary.shrink_to_fit(); @@ -219,6 +238,11 @@ std::vector::const_iterator PDFDictionary::find( return std::find_if(m_dictionary.cbegin(), m_dictionary.cend(), [&key](const DictionaryEntry& entry) { return entry.first == key; }); } +std::vector::iterator PDFDictionary::find(const QByteArray& key) +{ + return std::find_if(m_dictionary.begin(), m_dictionary.end(), [&key](const DictionaryEntry& entry) { return entry.first == key; }); +} + std::vector::const_iterator PDFDictionary::find(const char* key) const { return std::find_if(m_dictionary.cbegin(), m_dictionary.cend(), [&key](const DictionaryEntry& entry) { return entry.first == key; }); @@ -231,4 +255,59 @@ bool PDFStream::equals(const PDFObjectContent* other) const return m_dictionary.equals(&otherStream->m_dictionary) && m_content == otherStream->m_content; } +PDFObject PDFObjectManipulator::merge(PDFObject left, PDFObject right, MergeFlags flags) +{ + if (left.getType() != right.getType()) + { + return right; + } + + if (left.isDictionary()) + { + Q_ASSERT(right.isDictionary()); + + PDFDictionary targetDictionary = *left.getDictionary(); + const PDFDictionary& sourceDictionary = *right.getDictionary(); + + for (size_t i = 0, count = sourceDictionary.getCount(); i < count; ++i) + { + const QByteArray& key = sourceDictionary.getKey(i); + PDFObject value = merge(targetDictionary.get(key), sourceDictionary.getValue(i), flags); + targetDictionary.setEntry(key, qMove(value)); + } + + if (flags.testFlag(RemoveNullObjects)) + { + targetDictionary.removeNullObjects(); + } + + return PDFObject::createDictionary(std::make_shared(qMove(targetDictionary))); + } + else if (left.isArray() && flags.testFlag(ConcatenateArrays)) + { + // Concatenate arrays + const PDFArray* leftArray = left.getArray(); + const PDFArray* rightArray = right.getArray(); + + std::vector objects; + objects.reserve(leftArray->getCount() + rightArray->getCount()); + for (size_t i = 0, count = leftArray->getCount(); i < count; ++i) + { + objects.emplace_back(leftArray->getItem(i)); + } + for (size_t i = 0, count = rightArray->getCount(); i < count; ++i) + { + objects.emplace_back(rightArray->getItem(i)); + } + return PDFObject::createArray(std::make_shared(qMove(objects))); + } + + return right; +} + +PDFObject PDFObjectManipulator::removeNullObjects(PDFObject object) +{ + return merge(object, object, RemoveNullObjects); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfobject.h b/PdfForQtLib/sources/pdfobject.h index da4e916..2b3adda 100644 --- a/PdfForQtLib/sources/pdfobject.h +++ b/PdfForQtLib/sources/pdfobject.h @@ -95,6 +95,8 @@ public: constexpr inline PDFObject& operator=(const PDFObject&) = default; constexpr inline PDFObject& operator=(PDFObject&&) = default; + inline Type getType() const { return m_type; } + // Test operators inline bool isNull() const { return m_type == Type::Null; } inline bool isBool() const { return m_type == Type::Bool; } @@ -260,6 +262,12 @@ public: /// \param value Value void addEntry(QByteArray&& key, PDFObject&& value) { m_dictionary.emplace_back(std::move(key), std::move(value)); } + /// Sets entry value. If entry with given key doesn't exist, + /// then it is created. + /// \param key Key + /// \param value Value + void setEntry(const QByteArray& key, PDFObject&& value); + /// Returns count of items in the dictionary size_t getCount() const { return m_dictionary.size(); } @@ -274,6 +282,9 @@ public: /// \param index Zero-based index of value in the dictionary const PDFObject& getValue(size_t index) const { return m_dictionary[index].second; } + /// Removes null objects from dictionary + void removeNullObjects(); + /// Optimizes the dictionary for memory consumption virtual void optimize() override; @@ -283,6 +294,11 @@ private: /// \param key Key to be found std::vector::const_iterator find(const QByteArray& key) const; + /// Finds an item in the dictionary array, if the item is not in the dictionary, + /// then end iterator is returned. + /// \param key Key to be found + std::vector::iterator find(const QByteArray& key); + /// Finds an item in the dictionary array, if the item is not in the dictionary, /// then end iterator is returned. /// \param key Key to be found @@ -322,6 +338,34 @@ private: QByteArray m_content; }; +class PDFObjectManipulator +{ +public: + explicit PDFObjectManipulator() = delete; + + enum MergeFlag + { + RemoveNullObjects = 0x0001, ///< Remove null object from dictionaries + ConcatenateArrays = 0x0002, ///< Concatenate arrays instead of replace + }; + Q_DECLARE_FLAGS(MergeFlags, MergeFlag) + + /// Merges two objects. If object type is different, then object from right is used. + /// If both objects are dictionaries, then their content is merged, object \p right + /// has precedence over object \p left. If both objects are arrays, and concatenating + /// flag is turned on, then they are concatenated instead of replacing left array + /// by right array. If remove null objects flag is turend on, then null objects + /// are removed from dictionaries. + /// \param left Left, 'slave' object + /// \param right Right 'master' object, has priority over left + /// \param flags Merge flags + static PDFObject merge(PDFObject left, PDFObject right, MergeFlags flags); + + /// Remove null objects from all dictionaries + /// \param object Object + static PDFObject removeNullObjects(PDFObject object); +}; + } // namespace pdf #endif // PDFOBJECT_H diff --git a/PdfForQtLib/sources/pdfsecurityhandler.cpp b/PdfForQtLib/sources/pdfsecurityhandler.cpp index d1014ff..8da5424 100644 --- a/PdfForQtLib/sources/pdfsecurityhandler.cpp +++ b/PdfForQtLib/sources/pdfsecurityhandler.cpp @@ -68,7 +68,6 @@ private: PDFObjectReference m_reference; }; - void PDFDecryptObjectVisitor::visitNull() { m_objectStack.push_back(PDFObject::createNull()); diff --git a/generated_code_definition.xml b/generated_code_definition.xml index 5b3a812..d29ff79 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -48,21 +48,21 @@ title _QString - Title of the annotation + Title (it is displayed as title of popup window) subject _QString - Subject of the annotation (short description of the subject being adressed by the annotation) + Subject (short description of the subject being adressed by the annotation) contents _QString - Contents of the annotation (text displayed, for example, in the marked annotation dialog) + Contents (text displayed, for example, in the marked annotation dialog) Parameters @@ -123,7 +123,7 @@ Border DictionaryItemSimple - { 0.0, 0.0, borderWidth } + std::initializer_list<PDFReal>{ 0.0, 0.0, borderWidth } @@ -151,7 +151,7 @@ Contents DictionaryItemSimple - comment + contents @@ -201,7 +201,7 @@ Code _void - mergeTo(annotationObject, popupAnnotation); + mergeTo(annotationObject, updateAnnotationPopup); return annotationObject;