From 3a7ff58a31fae0603dc4a6d79197c7034608a93b Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 29 Jan 2023 18:08:33 +0100 Subject: [PATCH] 3D PDF: Code generator for PRC (initial commit) --- CodeGenerator/codegenerator.cpp | 248 ++++++++++++++++++++++++++ CodeGenerator/codegenerator.h | 56 ++++++ CodeGenerator/generatormainwindow.cpp | 51 ++++++ CodeGenerator/generatormainwindow.h | 8 + CodeGenerator/generatormainwindow.ui | 30 ++++ Pdf4QtLib/CMakeLists.txt | 2 + Pdf4QtLib/sources/pdf3d_prc.cpp | 30 ++++ Pdf4QtLib/sources/pdf3d_prc.h | 39 ++++ Pdf4QtLib/sources/pdf3d_u3d.cpp | 2 +- prc/prc_format.xml | 9 +- 10 files changed, 470 insertions(+), 5 deletions(-) create mode 100644 Pdf4QtLib/sources/pdf3d_prc.cpp create mode 100644 Pdf4QtLib/sources/pdf3d_prc.h diff --git a/CodeGenerator/codegenerator.cpp b/CodeGenerator/codegenerator.cpp index 5500b3e..22a76de 100644 --- a/CodeGenerator/codegenerator.cpp +++ b/CodeGenerator/codegenerator.cpp @@ -1913,4 +1913,252 @@ QString XFACodeGenerator::generateHeader() const return QString::fromUtf8(ba); } +void PRCCodeGenerator::generateCode(const QDomDocument& document, QString headerName, QString sourceName) +{ + QString startMark = "/* START GENERATED CODE */"; + QString endMark = "/* END GENERATED CODE */"; + + loadClasses(document); + + QFile headerFile(headerName); + if (headerFile.exists()) + { + if (headerFile.open(QFile::ReadOnly | QFile::Text)) + { + 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(); + 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 | QFile::Text)) + { + 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(); + QString allCode = frontPart + sourceGeneratedCode + backPart; + + sourceFile.open(QFile::WriteOnly | QFile::Truncate); + sourceFile.write(allCode.toUtf8()); + sourceFile.close(); + } + } +} + +void PRCCodeGenerator::loadClasses(const QDomDocument& document) +{ + QDomElement prcElement = document.firstChildElement("prc"); + + if (prcElement.isNull()) + { + return; + } + + QDomElement element = prcElement.firstChildElement("objects"); + + if (element.isNull()) + { + return; + } + + QDomNodeList childNodes = element.elementsByTagName("object"); + + for (int i = 0; i < childNodes.length(); ++i) + { + QDomNode node = childNodes.item(i); + QDomElement classElement = node.toElement(); + QString className = classElement.attribute("name"); + + Class myClass; + myClass.classType = className; + myClass.isFlat = classElement.attribute("flat") == "true"; + myClass.valueType = myClass.isFlat ? className : QString("std::shared_ptr<%1>").arg(myClass.classType); + + QDomNodeList loadItems = classElement.childNodes(); + for (int j = 0; j < loadItems.length(); ++j) + { + QDomElement loadElement = loadItems.item(j).toElement(); + + LoadItem loadItem; + loadItem.name = loadElement.attribute("name"); + loadItem.type = loadElement.attribute("type"); + loadItem.constant = loadElement.attribute("constant"); + loadItem.bits = loadElement.attribute("bits"); + + if (loadElement.tagName() == "field") + { + loadItem.loadItemType = Field; + } + if (loadElement.tagName() == "array") + { + loadItem.loadItemType = Array; + } + + if (loadElement.attribute("value") == "true") + { + myClass.isValue = true; + myClass.valueType = loadElement.attribute("type"); + } + + myClass.items.emplace_back(std::move(loadItem)); + } + + m_classes[className] = (std::move(myClass)); + } +} + +QString PRCCodeGenerator::generateHeader() const +{ + QByteArray ba; + { + QTextStream stream(&ba, QIODevice::WriteOnly); + stream.setEncoding(QStringConverter::Utf8); + stream.setRealNumberPrecision(3); + stream.setRealNumberNotation(QTextStream::FixedNotation); + + stream << Qt::endl << Qt::endl; + + for (const auto& item : m_classes) + { + const Class& myClass = item.second; + + if (myClass.isValue) + { + continue; + } + + stream << "class " << myClass.classType << Qt::endl; + stream << "{" << Qt::endl; + stream << "public:" << Qt::endl; + stream << QString(" explicit %1() = default;").arg(myClass.classType) << Qt::endl; + stream << QString(" ~%1() = default;").arg(myClass.classType) << Qt::endl << Qt::endl; + + // Generate getters/setters + for (const LoadItem& loadItem : myClass.items) + { + if (!loadItem.isFieldOrArray()) + { + continue; + } + + stream << QString(" const %1& %2() const { return %3; }").arg(getValueTypeForLoadItem(loadItem), getGetterFunctionNameForLoadItem(loadItem), getClassFieldNameForValueItem(loadItem)) << Qt::endl; + stream << QString(" void %2(%1 value) { return %3 = std::move(value); }").arg(getValueTypeForLoadItem(loadItem), getSetterFunctionNameForLoadItem(loadItem), getClassFieldNameForValueItem(loadItem)) << Qt::endl; + + stream << Qt::endl; + } + + stream << Qt::endl; + stream << "private:" << Qt::endl; + + // Generate fields + for (const LoadItem& loadItem : myClass.items) + { + if (!loadItem.isFieldOrArray()) + { + continue; + } + + stream << QString(" %1 %2 = %1();").arg(getValueTypeForLoadItem(loadItem), getClassFieldNameForValueItem(loadItem)) << Qt::endl; + } + + stream << "};" << Qt::endl << Qt::endl; + } + + stream << Qt::endl << Qt::endl; + } + + return QString::fromUtf8(ba); +} + +QString PRCCodeGenerator::generateSource() const +{ + return QString(); +} + +QString PRCCodeGenerator::getCamelCase(QString string) const +{ + QString str; + + bool upperCase = false; + for (QChar ch : string) + { + if (ch == '_') + { + upperCase = true; + continue; + } + + if (upperCase) + { + upperCase = false; + ch = ch.toUpper(); + } + + str += ch; + } + + return str; +} + +QString PRCCodeGenerator::getValueTypeForLoadItem(const LoadItem& item) const +{ + auto it = m_classes.find(item.type); + if (it != m_classes.cend()) + { + const Class& myClass = (*it).second; + QString valueType = myClass.valueType; + + if (item.isField()) + { + return valueType; + } + + if (item.isArray()) + { + return QString("std::vector<%1>").arg(valueType); + } + } + else + { + qWarning() << "Class " << item.type << " not found!"; + } + + return QString(); +} + +QString PRCCodeGenerator::getClassFieldNameForValueItem(const LoadItem& item) const +{ + return QString("m_") + getCamelCase(item.name); +} + +QString PRCCodeGenerator::getGetterFunctionNameForLoadItem(const LoadItem& item) const +{ + return getCamelCase(QString("is_") + item.name); +} + +QString PRCCodeGenerator::getSetterFunctionNameForLoadItem(const LoadItem& item) const +{ + return getCamelCase(QString("set_") + item.name); +} + } diff --git a/CodeGenerator/codegenerator.h b/CodeGenerator/codegenerator.h index a6f926f..0467b6b 100644 --- a/CodeGenerator/codegenerator.h +++ b/CodeGenerator/codegenerator.h @@ -498,6 +498,62 @@ private: std::set m_usedTypes; }; +class PRCCodeGenerator +{ +public: + PRCCodeGenerator() = default; + + void generateCode(const QDomDocument& document, QString headerName, QString sourceName); + +private: + + enum LoadItemType + { + Field, + Array + }; + + struct LoadItem + { + bool isField() const { return loadItemType == Field; } + bool isArray() const { return loadItemType == Array; } + bool isFieldOrArray() const { return isField() || loadItemType == Array; } + + LoadItemType loadItemType = Field; + QString name; + QString type; + QString constant; + QString bits; + }; + + struct Class + { + QString classType; + QString valueType; + + /// Use class by value, not by shared pointer? + bool isFlat = false; + + /// Use value type instead of the class? + bool isValue = false; + + std::vector items; + }; + + void loadClasses(const QDomDocument& document); + + QString generateHeader() const; + QString generateSource() const; + + QString getCamelCase(QString string) const; + QString getValueTypeForLoadItem(const LoadItem& item) const; + QString getClassFieldNameForValueItem(const LoadItem& item) const; + QString getGetterFunctionNameForLoadItem(const LoadItem& item) const; + QString getSetterFunctionNameForLoadItem(const LoadItem& item) const; + + std::map m_classes; +}; + } // namespace codegen Q_DECLARE_METATYPE(codegen::GeneratedCodeStorage*) diff --git a/CodeGenerator/generatormainwindow.cpp b/CodeGenerator/generatormainwindow.cpp index 5f487ff..62d187d 100644 --- a/CodeGenerator/generatormainwindow.cpp +++ b/CodeGenerator/generatormainwindow.cpp @@ -91,6 +91,9 @@ void GeneratorMainWindow::saveSettings() settings.setValue("XFAdefinitionFileName", m_XFAdefinitionFileName); settings.setValue("XFAheaderFileName", m_XFAheaderFileName); settings.setValue("XFAsourceFileName", m_XFAsourceFileName); + settings.setValue("PRCdefinitionFileName", m_PRCdefinitionFileName); + settings.setValue("PRCheaderFileName", m_PRCheaderFileName); + settings.setValue("PRCsourceFileName", m_PRCsourceFileName); } void GeneratorMainWindow::loadGeneratedSettings() @@ -362,6 +365,9 @@ void GeneratorMainWindow::loadSettings() m_XFAdefinitionFileName = settings.value("XFAdefinitionFileName", QVariant()).toString(); m_XFAheaderFileName = settings.value("XFAheaderFileName", QVariant()).toString(); m_XFAsourceFileName = settings.value("XFAsourceFileName", QVariant()).toString(); + m_PRCdefinitionFileName = settings.value("PRCdefinitionFileName", QVariant()).toString(); + m_PRCheaderFileName = settings.value("PRCheaderFileName", QVariant()).toString(); + m_PRCsourceFileName = settings.value("PRCsourceFileName", QVariant()).toString(); } void GeneratorMainWindow::save(const QString& fileName) @@ -572,3 +578,48 @@ void GeneratorMainWindow::on_actionGenerate_XFA_code_triggered() generator.generateCode(document, m_XFAheaderFileName, m_XFAsourceFileName); } } + +void GeneratorMainWindow::on_actionSet_code_header_PRC_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp header"), QString(), "cpp header (*.h)"); + if (!fileName.isEmpty()) + { + m_PRCheaderFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionSet_code_source_PRC_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp source"), QString(), "cpp source (*.cpp)"); + if (!fileName.isEmpty()) + { + m_PRCsourceFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionSet_PRC_description_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select xml definition"), QString(), "XML file (*.xml)"); + if (!fileName.isEmpty()) + { + m_PRCdefinitionFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionGenerate_PRC_code_triggered() +{ + codegen::PRCCodeGenerator generator; + + QFile file(m_PRCdefinitionFileName); + if (file.open(QFile::ReadOnly)) + { + QDomDocument document; + document.setContent(&file); + file.close(); + + generator.generateCode(document, m_PRCheaderFileName, m_PRCsourceFileName); + } +} diff --git a/CodeGenerator/generatormainwindow.h b/CodeGenerator/generatormainwindow.h index 2828b24..aab3bab 100644 --- a/CodeGenerator/generatormainwindow.h +++ b/CodeGenerator/generatormainwindow.h @@ -84,6 +84,10 @@ private slots: void on_actionSet_code_source_XFA_triggered(); void on_actionGenerate_XFA_code_triggered(); void on_actionSet_XFA_description_triggered(); + void on_actionSet_code_header_PRC_triggered(); + void on_actionSet_code_source_PRC_triggered(); + void on_actionGenerate_PRC_code_triggered(); + void on_actionSet_PRC_description_triggered(); private: void loadSettings(); @@ -112,6 +116,10 @@ private: QString m_XFAdefinitionFileName; QString m_XFAheaderFileName; QString m_XFAsourceFileName; + + QString m_PRCdefinitionFileName; + QString m_PRCheaderFileName; + QString m_PRCsourceFileName; }; #endif // GENERATORMAINWINDOW_H diff --git a/CodeGenerator/generatormainwindow.ui b/CodeGenerator/generatormainwindow.ui index 2114f57..6a4625c 100644 --- a/CodeGenerator/generatormainwindow.ui +++ b/CodeGenerator/generatormainwindow.ui @@ -250,9 +250,19 @@ + + + PRC + + + + + + + @@ -314,6 +324,26 @@ Set XFA description + + + Set code header PRC + + + + + Set code source PRC + + + + + Generate PRC code + + + + + Set PRC description + + diff --git a/Pdf4QtLib/CMakeLists.txt b/Pdf4QtLib/CMakeLists.txt index 7a3d38e..52a2e25 100644 --- a/Pdf4QtLib/CMakeLists.txt +++ b/Pdf4QtLib/CMakeLists.txt @@ -101,6 +101,8 @@ add_library(Pdf4QtLib SHARED sources/pdfobjecteditorwidget_impl.h sources/pdfdocumentsanitizer.h sources/pdfdocumentsanitizer.cpp + sources/pdf3d_prc.h + sources/pdf3d_prc.cpp cmaps.qrc ) diff --git a/Pdf4QtLib/sources/pdf3d_prc.cpp b/Pdf4QtLib/sources/pdf3d_prc.cpp new file mode 100644 index 0000000..845f412 --- /dev/null +++ b/Pdf4QtLib/sources/pdf3d_prc.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2023 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#include "pdf3d_prc.h" + +namespace pdf +{ + +namespace prc +{ + +/* START GENERATED CODE *//* END GENERATED CODE */ + +} // namespace prc + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdf3d_prc.h b/Pdf4QtLib/sources/pdf3d_prc.h new file mode 100644 index 0000000..fd83ded --- /dev/null +++ b/Pdf4QtLib/sources/pdf3d_prc.h @@ -0,0 +1,39 @@ +// Copyright (C) 2023 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT 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 +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 PDF4QT. If not, see . + +#ifndef PDF3D_PRC_H +#define PDF3D_PRC_H + +#include "pdfglobal.h" + +#include + +namespace pdf +{ + +namespace prc +{ + +/* START GENERATED CODE */ + +/* END GENERATED CODE */ + +} // namespace prc + +} // namespace pdf + +#endif // PDF3D_PRC_H diff --git a/Pdf4QtLib/sources/pdf3d_u3d.cpp b/Pdf4QtLib/sources/pdf3d_u3d.cpp index 605c3ef..4d7806e 100644 --- a/Pdf4QtLib/sources/pdf3d_u3d.cpp +++ b/Pdf4QtLib/sources/pdf3d_u3d.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Jakub Melka +// Copyright (C) 2022-2023 Jakub Melka // // This file is part of PDF4QT. // diff --git a/prc/prc_format.xml b/prc/prc_format.xml index 7422b1d..f98a15d 100644 --- a/prc/prc_format.xml +++ b/prc/prc_format.xml @@ -1,3 +1,5 @@ + + -