From 0efc4bb40b76aad972bcf711eaa1ae1aa47e9b3e Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 10 Apr 2020 20:52:05 +0200 Subject: [PATCH] 3D annotation - finishing --- PdfForQtLib/sources/pdfannotation.cpp | 38 +++++++++ PdfForQtLib/sources/pdfannotation.h | 24 ++++++ PdfForQtLib/sources/pdfdocument.cpp | 20 +++++ PdfForQtLib/sources/pdfdocument.h | 24 ++++++ PdfForQtLib/sources/pdfmultimedia.cpp | 105 +++++++++++++++++++++++- PdfForQtLib/sources/pdfmultimedia.h | 114 +++++++++++++++++++++++++- 6 files changed, 323 insertions(+), 2 deletions(-) diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index 5bd257e..98279be 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -539,6 +539,44 @@ PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject annotation->m_relativeVerticalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "V", 0.0); } } + else if (subtype == "3D") + { + PDF3DAnnotation* annotation = new PDF3DAnnotation(); + result.reset(annotation); + + annotation->m_stream = PDF3DStream::parse(storage, dictionary->get("3DD")); + + const std::vector& views = annotation->getStream().getViews(); + PDFObject defaultViewObject = storage->getObject(dictionary->get("DV")); + if (defaultViewObject.isDictionary()) + { + annotation->m_defaultView = PDF3DView::parse(storage, defaultViewObject); + } + else if (defaultViewObject.isInt()) + { + PDFInteger index = defaultViewObject.getInteger(); + if (index >= 0 && index < PDFInteger(views.size())) + { + annotation->m_defaultView = views[index]; + } + } + else if (defaultViewObject.isName() && !views.empty()) + { + QByteArray name = defaultViewObject.getString(); + if (name == "F") + { + annotation->m_defaultView = views.front(); + } + else if (name == "L") + { + annotation->m_defaultView = views.back(); + } + } + + annotation->m_activation = PDF3DActivation::parse(storage, dictionary->get("3DA")); + annotation->m_interactive = loader.readBooleanFromDictionary(dictionary, "3DI", true); + annotation->m_viewBox = loader.readRectangle(dictionary->get("3DB"), QRectF()); + } else if (subtype == "RichMedia") { PDFRichMediaAnnotation* annotation = new PDFRichMediaAnnotation(); diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index ffbf723..2cc8119 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -1183,6 +1183,30 @@ private: PDFReal m_relativeVerticalOffset = 0.0; }; +/// 3D annotations represents 3D scene, which can be viewed in the application. +class PDF3DAnnotation : public PDFAnnotation +{ +public: + inline explicit PDF3DAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::_3D; } + + const PDF3DStream& getStream() const { return m_stream; } + const std::optional& getDefaultView() const { return m_defaultView; } + const PDF3DActivation& getActivation() const { return m_activation; } + bool isInteractive() const { return m_interactive; } + QRectF getViewBox() const { return m_viewBox; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject object); + + PDF3DStream m_stream; + std::optional m_defaultView; + PDF3DActivation m_activation; + bool m_interactive = true; + QRectF m_viewBox; +}; + /// Rich media annotations can be video, audio, or other multimedia presentations. /// The application should provide additional functionality to control rich media, /// such as buttons to play/pause/stop video etc. This annotation consists diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index c859a83..3a6a551 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -635,6 +635,26 @@ std::vector PDFDocumentDataLoaderDecorator::readStringArrayFromDicti return std::vector(); } +QStringList PDFDocumentDataLoaderDecorator::readTextStringList(const PDFObject& object) +{ + QStringList result; + + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + const size_t count = array->getCount(); + result.reserve(int(count)); + + for (size_t i = 0; i < count; ++i) + { + result << readTextString(array->getItem(i), QString()); + } + } + + return result; +} + std::vector PDFDocumentDataLoaderDecorator::readStringArray(const PDFObject& object) const { const PDFObject& dereferencedObject = m_storage->getObject(object); diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 884795c..19e123a 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -337,6 +337,30 @@ public: /// \param key Entry key std::vector readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; + /// Reads string list. If error occurs, empty list is returned. + QStringList readTextStringList(const PDFObject& object); + + /// Reads list of object, using parse function defined in object + template + std::vector readObjectList(PDFObject object) + { + std::vector result; + object = m_storage->getObject(object); + if (object.isArray()) + { + const PDFArray* array = object.getArray(); + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + result.emplace_back(Object::parse(m_storage, array->getItem(i))); + } + } + + return result; + } + private: const PDFObjectStorage* m_storage; }; diff --git a/PdfForQtLib/sources/pdfmultimedia.cpp b/PdfForQtLib/sources/pdfmultimedia.cpp index c1fdbee..1dd90dd 100644 --- a/PdfForQtLib/sources/pdfmultimedia.cpp +++ b/PdfForQtLib/sources/pdfmultimedia.cpp @@ -1174,7 +1174,7 @@ PDF3DProjection PDF3DProjection::parse(const PDFObjectStorage* storage, PDFObjec }; PDFDocumentDataLoaderDecorator loader(storage); - result.m_projection = loader.readEnumByName(dictionary->get("Subtype"), projections.cbegin(), projections.cend(), Projection::Orthographic); + result.m_projection = loader.readEnumByName(dictionary->get("Subtype"), projections.cbegin(), projections.cend(), Projection::Perspective); result.m_clippingStyle = loader.readEnumByName(dictionary->get("CS"), clippingStyles.cbegin(), clippingStyles.cend(), ClippingStyle::Automatic); result.m_near = loader.readNumberFromDictionary(dictionary, "N", 0.0); result.m_far = loader.readNumberFromDictionary(dictionary, "F", qInf()); @@ -1191,4 +1191,107 @@ PDF3DProjection PDF3DProjection::parse(const PDFObjectStorage* storage, PDFObjec return result; } +PDF3DView PDF3DView::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DView result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + constexpr const std::array, 2> matrixSelection = { + std::pair{ "M", MatrixSelection::M }, + std::pair{ "U3D", MatrixSelection::U3D } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + result.m_externalName = loader.readTextStringFromDictionary(dictionary, "XN", QString()); + result.m_internalName = loader.readTextStringFromDictionary(dictionary, "IN", QString()); + result.m_matrixSelection = loader.readEnumByName(dictionary->get("MS"), matrixSelection.cbegin(), matrixSelection.cend(), MatrixSelection::M); + result.m_cameraToWorld = PDF3DAuxiliaryParser::parseMatrix4x4(storage, dictionary->get("C2W")); + result.m_U3Dpath = loader.readTextStringList(dictionary->get("U3DPath")); + result.m_cameraDistance = loader.readNumberFromDictionary(dictionary, "CO", 0.0); + result.m_projection = PDF3DProjection::parse(storage, dictionary->get("P")); + result.m_overlay = dictionary->get("O"); + result.m_background = PDF3DBackground::parse(storage, dictionary->get("BG")); + result.m_renderMode = PDF3DRenderMode::parse(storage, dictionary->get("RM")); + result.m_lightingScheme = PDF3DLightingScheme::parse(storage, dictionary->get("LS")); + result.m_crossSections = loader.readObjectList(dictionary->get("SA")); + result.m_nodes = loader.readObjectList(dictionary->get("NA")); + result.m_nodesRestore = loader.readBooleanFromDictionary(dictionary, "NR", false); + } + + return result; +} + +PDF3DAnimation PDF3DAnimation::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DAnimation result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + constexpr const std::array, 3> animations = { + std::pair{ "None", Animation::None }, + std::pair{ "Linear", Animation::Linear }, + std::pair{ "Oscillating", Animation::Oscillating } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + result.m_animation = loader.readEnumByName(dictionary->get("Subtype"), animations.cbegin(), animations.cend(), Animation::None); + result.m_playCount = loader.readIntegerFromDictionary(dictionary, "PC", -1); + result.m_speed = loader.readNumberFromDictionary(dictionary, "TM", 1.0); + } + + return result; +} + +PDF3DStream PDF3DStream::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DStream result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + constexpr const std::array, 2> types = { + std::pair{ "U3D", Type::U3D }, + std::pair{ "PRC", Type::PRC } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + result.m_stream = object; + result.m_type = loader.readEnumByName(dictionary->get("Subtype"), types.cbegin(), types.cend(), Type::Invalid); + result.m_views = loader.readObjectList(dictionary->get("VA")); + + PDFObject defaultViewObject = storage->getObject(dictionary->get("DV")); + if (defaultViewObject.isDictionary()) + { + result.m_defaultView = PDF3DView::parse(storage, defaultViewObject); + } + else if (defaultViewObject.isInt()) + { + PDFInteger index = defaultViewObject.getInteger(); + if (index >= 0 && index < PDFInteger(result.m_views.size())) + { + result.m_defaultView = result.m_views[index]; + } + } + else if (defaultViewObject.isName() && !result.m_views.empty()) + { + QByteArray name = defaultViewObject.getString(); + if (name == "F") + { + result.m_defaultView = result.m_views.front(); + } + else if (name == "L") + { + result.m_defaultView = result.m_views.back(); + } + } + + result.m_resources = dictionary->get("Resources"); + result.m_onInstantiateJavascript = dictionary->get("OnInstantiate"); + result.m_animation = PDF3DAnimation::parse(storage, dictionary->get("AN")); + result.m_colorSpace = dictionary->get("ColorSpace"); + } + + return result; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfmultimedia.h b/PdfForQtLib/sources/pdfmultimedia.h index 2afdd05..519a6db 100644 --- a/PdfForQtLib/sources/pdfmultimedia.h +++ b/PdfForQtLib/sources/pdfmultimedia.h @@ -1139,7 +1139,7 @@ public: static PDF3DProjection parse(const PDFObjectStorage* storage, PDFObject object); private: - Projection m_projection = Projection::Orthographic; + Projection m_projection = Projection::Perspective; ClippingStyle m_clippingStyle = ClippingStyle::Automatic; PDFReal m_near = 0.0; PDFReal m_far = qInf(); @@ -1150,6 +1150,118 @@ private: ScaleMode m_scaleMode = ScaleMode::Absolute; }; +/// 3D PDF view +class PDF3DView +{ +public: + explicit inline PDF3DView() = default; + + enum class MatrixSelection + { + M, + U3D + }; + + const QString& getExternalName() const { return m_externalName; } + const QString& getInternalName() const { return m_internalName; } + MatrixSelection getMatrixSelection() const{ return m_matrixSelection; } + const QMatrix4x4& getCameraToWorld() const { return m_cameraToWorld; } + const QStringList& getU3DPath() const { return m_U3Dpath; } + PDFReal getCameraDistance() const { return m_cameraDistance; } + const PDF3DProjection& getProjection() const { return m_projection; } + const PDFObject& getOverlay() const { return m_overlay; } + const PDF3DBackground& getBackground() const { return m_background; } + const PDF3DRenderMode& getRenderMode() const { return m_renderMode; } + const PDF3DLightingScheme& getLightingScheme() const { return m_lightingScheme; } + const std::vector& getCrossSections() const { return m_crossSections; } + const std::vector& getNodes() const { return m_nodes; } + bool getNodesRestore() const { return m_nodesRestore; } + + /// Creates a new 3D view from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DView parse(const PDFObjectStorage* storage, PDFObject object); + +private: + QString m_externalName; + QString m_internalName; + MatrixSelection m_matrixSelection = MatrixSelection::M; + QMatrix4x4 m_cameraToWorld; + QStringList m_U3Dpath; + PDFReal m_cameraDistance = 0.0; + PDF3DProjection m_projection; + PDFObject m_overlay; + PDF3DBackground m_background; + PDF3DRenderMode m_renderMode; + PDF3DLightingScheme m_lightingScheme; + std::vector m_crossSections; + std::vector m_nodes; + bool m_nodesRestore = false; +}; + +/// 3D PDF animation +class PDF3DAnimation +{ +public: + explicit inline PDF3DAnimation() = default; + + enum class Animation + { + None, + Linear, + Oscillating + }; + + Animation getAnimation() const { return m_animation; } + PDFInteger getPlayCount() const { return m_playCount; } + PDFReal getSpeed() const { return m_speed; } + + /// Creates a new 3D animation from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DAnimation parse(const PDFObjectStorage* storage, PDFObject object); + +private: + Animation m_animation = Animation::None; + PDFInteger m_playCount = -1; + PDFReal m_speed = 1; +}; + +/// 3D PDF stream +class PDF3DStream +{ +public: + explicit inline PDF3DStream() = default; + + enum class Type + { + Invalid, + U3D, + PRC + }; + + PDFObject getStream() const { return m_stream; } + Type getType() const { return m_type; } + const std::vector& getViews() const { return m_views; } + const std::optional& getDefaultView() const { return m_defaultView; } + PDFObject getResources() const { return m_resources; } + PDFObject getOnInstantiateJavascript() const { return m_onInstantiateJavascript; } + PDF3DAnimation getAnimation() const { return m_animation; } + PDFObject getColorSpace() const { return m_colorSpace; } + + /// Creates a new 3D stream from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DStream parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFObject m_stream; + Type m_type = Type::Invalid; + std::vector m_views; + std::optional m_defaultView; + PDFObject m_resources; + PDFObject m_onInstantiateJavascript; + PDF3DAnimation m_animation; + PDFObject m_colorSpace; +}; + } // namespace pdf #endif // PDFMULTIMEDIA_H