diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp index 56810a8..36d6c00 100644 --- a/PdfForQtLib/sources/pdfcatalog.cpp +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -973,4 +973,151 @@ std::optional PDFLegalAttestation::parse(const PDFObjectSto return result; } +PDFDocumentRequirements::ValidationResult PDFDocumentRequirements::validate(Requirements supported) const +{ + ValidationResult result; + + QStringList unsatisfiedRequirements; + for (const RequirementEntry& entry : m_requirements) + { + if (entry.requirement == None) + { + // Unrecognized entry, just add the penalty + result.penalty += entry.penalty; + continue; + } + + if (!supported.testFlag(entry.requirement)) + { + result.penalty += entry.penalty; + unsatisfiedRequirements << getRequirementName(entry.requirement); + } + } + + if (!unsatisfiedRequirements.isEmpty()) + { + result.message = PDFTranslationContext::tr("Required features %1 are unsupported. Document processing can be limited.").arg(unsatisfiedRequirements.join(", ")); + } + + return result; +} + +QString PDFDocumentRequirements::getRequirementName(Requirement requirement) +{ + switch (requirement) + { + case OCInteract: + return PDFTranslationContext::tr("Optional Content User Interaction"); + case OCAutoStates: + return PDFTranslationContext::tr("Optional Content Usage"); + case AcroFormInteract: + return PDFTranslationContext::tr("Acrobat Forms"); + case Navigation: + return PDFTranslationContext::tr("Navigation"); + case Markup: + return PDFTranslationContext::tr("Markup Annotations"); + case _3DMarkup: + return PDFTranslationContext::tr("Markup of 3D Content"); + case Multimedia: + return PDFTranslationContext::tr("Multimedia"); + case U3D: + return PDFTranslationContext::tr("U3D Format of PDF 3D"); + case PRC: + return PDFTranslationContext::tr("PRC Format of PDF 3D"); + case Action: + return PDFTranslationContext::tr("Actions"); + case EnableJavaScripts: + return PDFTranslationContext::tr("JavaScript"); + case Attachment: + return PDFTranslationContext::tr("Attached Files"); + case AttachmentEditing: + return PDFTranslationContext::tr("Attached Files Modification"); + case Collection: + return PDFTranslationContext::tr("Collections of Attached Files"); + case CollectionEditing: + return PDFTranslationContext::tr("Collections of Attached Files (editation)"); + case DigSigValidation: + return PDFTranslationContext::tr("Digital Signature Validation"); + case DigSig: + return PDFTranslationContext::tr("Apply Digital Signature"); + case DigSigMDP: + return PDFTranslationContext::tr("Digital Signature Validation (with MDP)"); + case RichMedia: + return PDFTranslationContext::tr("Rich Media"); + case Geospatial2D: + return PDFTranslationContext::tr("Geospatial 2D Features"); + case Geospatial3D: + return PDFTranslationContext::tr("Geospatial 3D Features"); + case DPartInteract: + return PDFTranslationContext::tr("Navigation for Document Parts"); + case SeparationSimulation: + return PDFTranslationContext::tr("Separation Simulation"); + case Transitions: + return PDFTranslationContext::tr("Transitions/Presentations"); + case Encryption: + return PDFTranslationContext::tr("Encryption"); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +PDFDocumentRequirements PDFDocumentRequirements::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + PDFDocumentRequirements requirements; + + PDFDocumentDataLoaderDecorator loader(storage); + requirements.m_requirements = loader.readObjectList(object); + + return requirements; +} + +PDFDocumentRequirements::RequirementEntry PDFDocumentRequirements::RequirementEntry::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + RequirementEntry entry; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + constexpr const std::array requirementTypes = { + std::pair{ "OCInteract", OCInteract }, + std::pair{ "OCAutoStates", OCAutoStates }, + std::pair{ "AcroFormInteract", AcroFormInteract }, + std::pair{ "Navigation", Navigation }, + std::pair{ "Markup", Markup }, + std::pair{ "3DMarkup", _3DMarkup }, + std::pair{ "Multimedia", Multimedia }, + std::pair{ "U3D", U3D }, + std::pair{ "PRC", PRC }, + std::pair{ "Action", Action }, + std::pair{ "EnableJavaScripts", EnableJavaScripts }, + std::pair{ "Attachment", Attachment }, + std::pair{ "AttachmentEditing", AttachmentEditing }, + std::pair{ "Collection", Collection }, + std::pair{ "CollectionEditing", CollectionEditing }, + std::pair{ "DigSigValidation", DigSigValidation }, + std::pair{ "DigSig", DigSig }, + std::pair{ "DigSigMDP", DigSigMDP }, + std::pair{ "RichMedia", RichMedia }, + std::pair{ "Geospatial2D", Geospatial2D }, + std::pair{ "Geospatial3D", Geospatial3D }, + std::pair{ "DPartInteract", DPartInteract }, + std::pair{ "SeparationSimulation", SeparationSimulation }, + std::pair{ "Transitions", Transitions }, + std::pair{ "Encryption", Encryption } + }; + + entry.requirement = loader.readEnumByName(dictionary->get("S"), requirementTypes.begin(), requirementTypes.end(), None); + entry.handler = dictionary->get("RH"); + entry.version = loader.readNameFromDictionary(dictionary, "V"); + entry.penalty = loader.readIntegerFromDictionary(dictionary, "Penalty", 100); + } + + return entry; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h index c3f52a3..5a6dce0 100644 --- a/PdfForQtLib/sources/pdfcatalog.h +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -450,6 +450,82 @@ private: QString m_attestation; }; +/// Document can contain requirements for viewer application. This class +/// verifies, if this library and viewer application satisfies these requirements +/// and returns result. +class PDFFORQTLIBSHARED_EXPORT PDFDocumentRequirements +{ +public: + + enum Requirement + { + None = 0x00000000, + OCInteract = 0x00000001, + OCAutoStates = 0x00000002, + AcroFormInteract = 0x00000004, + Navigation = 0x00000008, + Markup = 0x00000010, + _3DMarkup = 0x00000020, + Multimedia = 0x00000040, + U3D = 0x00000080, + PRC = 0x00000100, + Action = 0x00000200, + EnableJavaScripts = 0x00000400, + Attachment = 0x00000800, + AttachmentEditing = 0x00001000, + Collection = 0x00002000, + CollectionEditing = 0x00004000, + DigSigValidation = 0x00008000, + DigSig = 0x00010000, + DigSigMDP = 0x00020000, + RichMedia = 0x00040000, + Geospatial2D = 0x00080000, + Geospatial3D = 0x00100000, + DPartInteract = 0x00200000, + SeparationSimulation = 0x00400000, + Transitions = 0x00800000, + Encryption = 0x01000000 + }; + + Q_DECLARE_FLAGS(Requirements, Requirement) + + struct RequirementEntry + { + Requirement requirement = None; + PDFInteger penalty = 100; + QByteArray version; + PDFObject handler; + + static RequirementEntry parse(const PDFObjectStorage* storage, const PDFObject& object); + }; + + struct ValidationResult + { + Requirements unsatisfied = None; + PDFInteger penalty = 0; + QString message; + + bool isOk() const { return penalty < 100; } + bool isError() const { return !isOk(); } + bool isWarning() const { return isOk() && !message.isEmpty(); } + }; + + /// Validates requirements against supported requirements + ValidationResult validate(Requirements supported) const; + + /// Returns string version of requirement + static QString getRequirementName(Requirement requirement); + + /// Parses document requirements. If error occurs, empty + /// document requirements are returned. + /// \param storage Storage + /// \param object Object + static PDFDocumentRequirements parse(const PDFObjectStorage* storage, const PDFObject& object); + +private: + std::vector m_requirements; +}; + class PDFFORQTLIBSHARED_EXPORT PDFCatalog { public: diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 6f63ef9..39128cd 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -1051,6 +1051,23 @@ void PDFViewerMainWindow::onDocumentReadingFinished() pdf::PDFModifiedDocument document(m_pdfDocument.data(), m_optionalContentActivity); setDocument(document); + pdf::PDFDocumentRequirements requirements = pdf::PDFDocumentRequirements::parse(&m_pdfDocument->getStorage(), m_pdfDocument->getCatalog()->getRequirements()); + constexpr pdf::PDFDocumentRequirements::Requirements requirementFlags = pdf::PDFDocumentRequirements::OCInteract | + pdf::PDFDocumentRequirements::OCAutoStates | + pdf::PDFDocumentRequirements::Navigation | + pdf::PDFDocumentRequirements::Attachment | + pdf::PDFDocumentRequirements::DigSigValidation | + pdf::PDFDocumentRequirements::Encryption; + pdf::PDFDocumentRequirements::ValidationResult requirementResult = requirements.validate(requirementFlags); + if (requirementResult.isError()) + { + QMessageBox::critical(this, tr("PDF Viewer"), requirementResult.message); + } + else if (requirementResult.isWarning()) + { + QMessageBox::warning(this, tr("PDF Viewer"), requirementResult.message); + } + statusBar()->showMessage(tr("Document '%1' was successfully loaded!").arg(m_fileInfo.fileName), 4000); break; }