diff --git a/PdfForQtLib/sources/pdfmultimedia.cpp b/PdfForQtLib/sources/pdfmultimedia.cpp index 5d0051c..c1fdbee 100644 --- a/PdfForQtLib/sources/pdfmultimedia.cpp +++ b/PdfForQtLib/sources/pdfmultimedia.cpp @@ -31,6 +31,74 @@ constexpr const std::array, 4> richMediaTy std::pair{ "Video", RichMediaType::Video } }; +class PDF3DAuxiliaryParser +{ +public: + PDF3DAuxiliaryParser() = delete; + + /// Parses 4x4 transformation matrix + /// \param storage Storage + /// \param object Object + static QMatrix4x4 parseMatrix4x4(const PDFObjectStorage* storage, PDFObject object); + + /// Parses 3D annotation color + static QColor parseColor(const PDFObjectStorage* storage, PDFObject object, QColor defaultColor); +}; + +QMatrix4x4 PDF3DAuxiliaryParser::parseMatrix4x4(const PDFObjectStorage* storage, PDFObject object) +{ + QMatrix4x4 matrix; + + PDFDocumentDataLoaderDecorator loader(storage); + std::vector elements = loader.readNumberArray(object); + if (elements.size() == 12) + { + const PDFReal a = elements[ 0]; + const PDFReal b = elements[ 1]; + const PDFReal c = elements[ 2]; + const PDFReal d = elements[ 3]; + const PDFReal e = elements[ 4]; + const PDFReal f = elements[ 5]; + const PDFReal g = elements[ 6]; + const PDFReal h = elements[ 7]; + const PDFReal i = elements[ 8]; + const PDFReal tx = elements[ 9]; + const PDFReal ty = elements[10]; + const PDFReal tz = elements[11]; + + matrix = QMatrix4x4(a, b, c, 0, + d, e, f, 0, + g, h, i, 0, + tx, ty, tz, 1.0); + } + + return matrix; +} + +QColor PDF3DAuxiliaryParser::parseColor(const PDFObjectStorage* storage, PDFObject object, QColor defaultColor) +{ + object = storage->getObject(object); + + // If color is invalid according to the specification, then return default value + if (object.isArray()) + { + const PDFArray* array = object.getArray(); + if (array->getCount() == 4) + { + PDFDocumentDataLoaderDecorator loader(storage); + if (loader.readName(array->getItem(0)) == "DeviceRGB") + { + const PDFReal r = loader.readNumber(array->getItem(1), 0.0); + const PDFReal g = loader.readNumber(array->getItem(2), 0.0); + const PDFReal b = loader.readNumber(array->getItem(3), 0.0); + return QColor::fromRgbF(r, g, b, 1.0); + } + } + } + + return defaultColor; +} + PDFSound PDFSound::parse(const PDFObjectStorage* storage, PDFObject object) { PDFSound result; @@ -876,7 +944,7 @@ PDF3DActivation PDF3DActivation::parse(const PDFObjectStorage* storage, PDFObjec std::pair{ "PV", TriggerMode::PageVisibility } }; - constexpr const std::array, 3> deactivationModes = { + constexpr const std::array, 3> deactivationModes = { std::pair{ "XD", TriggerMode::ExplicitlyByUserAction }, std::pair{ "PC", TriggerMode::PageFocus }, std::pair{ "PI", TriggerMode::PageVisibility } @@ -908,4 +976,219 @@ PDF3DActivation PDF3DActivation::parse(const PDFObjectStorage* storage, PDFObjec return result; } +PDF3DRenderMode PDF3DRenderMode::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DRenderMode result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + constexpr const std::array, 15> renderModes = { + std::pair{ "Solid", RenderMode::Solid }, + std::pair{ "SolidWireframe", RenderMode::SolidWireframe }, + std::pair{ "Transparent", RenderMode::Transparent }, + std::pair{ "TransparentWireframe", RenderMode::TransparentWireframe }, + std::pair{ "BoundingBox", RenderMode::BoundingBox }, + std::pair{ "TransparentBoundingBox", RenderMode::TransparentBoundingBox }, + std::pair{ "TransparentBoundingBoxOutline", RenderMode::TransparentBoundingBoxOutline }, + std::pair{ "Wireframe", RenderMode::Wireframe }, + std::pair{ "ShadedWireframe", RenderMode::ShadedWireframe }, + std::pair{ "HiddenWireframe", RenderMode::HiddenWireframe }, + std::pair{ "Vertices", RenderMode::Vertices }, + std::pair{ "ShadedVertices", RenderMode::ShadedVertices }, + std::pair{ "Illustration", RenderMode::Illustration }, + std::pair{ "SolidOutline", RenderMode::SolidOutline }, + std::pair{ "ShadedIllustration", RenderMode::ShadedIllustration } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + result.m_renderMode = loader.readEnumByName(dictionary->get("Subtype"), renderModes.cbegin(), renderModes.cend(), RenderMode::Solid); + result.m_auxiliaryColor = PDF3DAuxiliaryParser::parseColor(storage, dictionary->get("AC"), Qt::black); + result.m_faceColor = PDF3DAuxiliaryParser::parseColor(storage, dictionary->get("FC"), Qt::black); + result.m_faceColorMode = (loader.readName(dictionary->get("FC")) != "BG") ? FaceColorMode::Color : FaceColorMode::BG; + result.m_opacity = loader.readNumberFromDictionary(dictionary, "O", 0.5); + result.m_creaseAngle = loader.readNumberFromDictionary(dictionary, "CV", 45.0); + } + + return result; +} + +PDF3DNode PDF3DNode::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DNode result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_name = loader.readTextStringFromDictionary(dictionary, "N", QString()); + if (dictionary->hasKey("O")) + { + result.m_opacity = loader.readNumberFromDictionary(dictionary, "O", 1.0); + } + if (dictionary->hasKey("V")) + { + result.m_visibility = loader.readBooleanFromDictionary(dictionary, "V", true); + } + if (dictionary->hasKey("M")) + { + result.m_matrix = PDF3DAuxiliaryParser::parseMatrix4x4(storage, dictionary->get("M")); + } + result.m_instance = loader.readReferenceFromDictionary(dictionary, "Instance"); + result.m_data = loader.readTextStringFromDictionary(dictionary, "Data", QString()); + if (dictionary->hasKey("RM")) + { + result.m_renderMode = PDF3DRenderMode::parse(storage, dictionary->get("RM")); + } + } + + return result; +} + +PDF3DCrossSection PDF3DCrossSection::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DCrossSection result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + loader.readNumberArray(dictionary->get("C"), result.m_centerOfRotation.begin(), result.m_centerOfRotation.end()); + + PDFObject axesRotation = storage->getObject(dictionary->get("O")); + if (const PDFArray* rotations = axesRotation.getArray()) + { + if (rotations->getCount() == 3) + { + PDFObject xObject = storage->getObject(rotations->getItem(0)); + PDFObject yObject = storage->getObject(rotations->getItem(1)); + PDFObject zObject = storage->getObject(rotations->getItem(2)); + + auto readValue = [&result, &loader](const PDFObject& object, int index, Direction direction) + { + if (object.isNull()) + { + result.m_perpendicularDirection = direction; + result.m_rotationAngles[index] = 0.0; + } + else + { + result.m_rotationAngles[index] = loader.readNumber(object, 0.0); + } + }; + + readValue(xObject, 0, Direction::X); + readValue(yObject, 1, Direction::Y); + readValue(zObject, 2, Direction::Z); + } + } + + result.m_cutPlaneOpacity = loader.readNumberFromDictionary(dictionary, "PO", 0.5); + result.m_cutPlaneColor = PDF3DAuxiliaryParser::parseColor(storage, dictionary->get("PC"), Qt::white); + result.m_intersectionVisibility = loader.readBooleanFromDictionary(dictionary, "IV", false); + result.m_intersectionColor = PDF3DAuxiliaryParser::parseColor(storage, dictionary->get("IC"), Qt::green); + result.m_showTransparent = loader.readBooleanFromDictionary(dictionary, "ST", false); + result.m_sectionCapping = loader.readBooleanFromDictionary(dictionary, "SC", false); + } + + return result; +} + +PDF3DLightingScheme PDF3DLightingScheme::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DLightingScheme result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + constexpr const std::array, 12> lightingSchemes = { + std::pair{ "Artwork", LightingScheme::Artwork }, + std::pair{ "None", LightingScheme::None }, + std::pair{ "White", LightingScheme::White }, + std::pair{ "Day", LightingScheme::Day }, + std::pair{ "Night", LightingScheme::Night }, + std::pair{ "Hard", LightingScheme::Hard }, + std::pair{ "Primary", LightingScheme::Primary }, + std::pair{ "Blue", LightingScheme::Blue }, + std::pair{ "Red", LightingScheme::Red }, + std::pair{ "Cube", LightingScheme::Cube }, + std::pair{ "CAD", LightingScheme::CAD }, + std::pair{ "Headlamp", LightingScheme::Headlamp } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + result.m_scheme = loader.readEnumByName(dictionary->get("Subtype"), lightingSchemes.cbegin(), lightingSchemes.cend(), LightingScheme::Artwork); + } + + return result; +} + +PDF3DBackground PDF3DBackground::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DBackground result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + // This is a hack to parse color in same way as in another objects + PDFObject colorSpace = dictionary->get("CS"); + if (colorSpace.isNull()) + { + colorSpace = PDFObject::createName(std::make_shared("DeviceRGB")); + } + std::vector color = loader.readNumberArrayFromDictionary(dictionary, "C", { 1.0, 1.0, 1.0}); + PDFArray array; + array.appendItem(colorSpace); + for (PDFReal colorComponent : color) + { + array.appendItem(PDFObject::createReal(colorComponent)); + } + PDFObject colorObject = PDFObject::createArray(std::make_shared(qMove(array))); + + result.m_color = PDF3DAuxiliaryParser::parseColor(storage, colorObject, Qt::white); + result.m_entireAnnotation = loader.readBooleanFromDictionary(dictionary, "EA", false); + } + + return result; +} + +PDF3DProjection PDF3DProjection::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDF3DProjection result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + constexpr const std::array, 2> projections = { + std::pair{ "O", Projection::Orthographic }, + std::pair{ "P", Projection::Perspective } + }; + + constexpr const std::array, 2> clippingStyles = { + std::pair{ "XNF", ClippingStyle::Explicit }, + std::pair{ "ANF", ClippingStyle::Automatic } + }; + + constexpr const std::array, 5> scaleModes = { + std::pair{ "W", ScaleMode::W }, + std::pair{ "H", ScaleMode::H }, + std::pair{ "Min", ScaleMode::Min }, + std::pair{ "Max", ScaleMode::Max }, + std::pair{ "Absolute", ScaleMode::Absolute } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + result.m_projection = loader.readEnumByName(dictionary->get("Subtype"), projections.cbegin(), projections.cend(), Projection::Orthographic); + 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()); + result.m_fieldOfViewAngle = loader.readNumberFromDictionary(dictionary, "FOV", 90.0); + if (dictionary->hasKey("PS")) + { + result.m_projectionScalingDiameter = loader.readNumberFromDictionary(dictionary, "PS", 0.0); + result.m_projectionScaleMode = loader.readEnumByName(dictionary->get("PS"), scaleModes.cbegin(), scaleModes.cend(), ScaleMode::Value); + } + result.m_scaleFactor = loader.readNumberFromDictionary(dictionary, "OS", 1.0); + result.m_scaleMode = result.m_projectionScaleMode = loader.readEnumByName(dictionary->get("OB"), scaleModes.cbegin(), scaleModes.cend(), ScaleMode::Absolute); + } + + return result; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfmultimedia.h b/PdfForQtLib/sources/pdfmultimedia.h index 52c3953..2afdd05 100644 --- a/PdfForQtLib/sources/pdfmultimedia.h +++ b/PdfForQtLib/sources/pdfmultimedia.h @@ -22,6 +22,7 @@ #include "pdffile.h" #include +#include #include #include @@ -31,6 +32,8 @@ namespace pdf { class PDFObjectStorage; +using PDFPoint3D = std::array; + struct PDFMediaMultiLanguageTexts { static PDFMediaMultiLanguageTexts parse(const PDFObjectStorage* storage, PDFObject object); @@ -899,8 +902,8 @@ public: Windowed }; - TriggerMode getActivationTriggerMode() const { return m_activationMode; } - TriggerMode getDeactivationTriggerMode() const { return m_deactivationMode; } + TriggerMode getActivationTriggerMode() const { return m_activationTriggerMode; } + TriggerMode getDeactivationTriggerMode() const { return m_deactivationTriggerMode; } ActivationMode getActivationMode() const { return m_activationMode; } ActivationMode getDeactivationMode() const { return m_deactivationMode; } bool hasToolbar() const { return m_toolbar; } @@ -925,6 +928,228 @@ private: bool m_transparent = false; }; +/// 3D PDF render mode +class PDF3DRenderMode +{ +public: + explicit inline PDF3DRenderMode() = default; + + enum class RenderMode + { + Solid, + SolidWireframe, + Transparent, + TransparentWireframe, + BoundingBox, + TransparentBoundingBox, + TransparentBoundingBoxOutline, + Wireframe, + ShadedWireframe, + HiddenWireframe, + Vertices, + ShadedVertices, + Illustration, + SolidOutline, + ShadedIllustration + }; + + enum class FaceColorMode + { + BG, ///< Current background color + Color ///< Color specified + }; + + RenderMode getRenderMode() const { return m_renderMode; } + QColor getAuxiliaryColor() const { return m_auxiliaryColor; } + QColor getFaceColor() const { return m_faceColor; } + FaceColorMode getFaceColorMode() const { return m_faceColorMode; } + PDFReal getOpacity() const { return m_opacity; } + PDFReal getCreaseAngle() const { return m_creaseAngle; } + + /// Creates a new 3D render mode from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DRenderMode parse(const PDFObjectStorage* storage, PDFObject object); + +private: + RenderMode m_renderMode = RenderMode::Solid; + QColor m_auxiliaryColor = Qt::black; + QColor m_faceColor = Qt::black; + FaceColorMode m_faceColorMode = FaceColorMode::BG; + PDFReal m_opacity = 0.5; + PDFReal m_creaseAngle = 45.0; +}; + +/// 3D PDF node (part of 3D structure) +class PDF3DNode +{ +public: + explicit inline PDF3DNode() = default; + + const QString& getName() const { return m_name; } + const std::optional& getOpacity() const { return m_opacity; } + const std::optional& getVisibility() const { return m_visibility; } + const std::optional& getMatrix() const { return m_matrix; } + PDFObjectReference getInstance() const { return m_instance; } + const QString& getData() const { return m_data; } + const std::optional& getRenderMode() const { return m_renderMode; } + + /// Creates a new 3D node from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DNode parse(const PDFObjectStorage* storage, PDFObject object); + +private: + QString m_name; + std::optional m_opacity; + std::optional m_visibility; + std::optional m_matrix; + PDFObjectReference m_instance; + QString m_data; + std::optional m_renderMode; +}; + +/// 3D PDF Cross section +class PDF3DCrossSection +{ +public: + explicit inline PDF3DCrossSection() = default; + + enum class Direction + { + X, + Y, + Z + }; + + PDFPoint3D getCenterOfRotation() const { return m_centerOfRotation; } + PDFPoint3D getRotationAngles() const { return m_rotationAngles; } + Direction getPerpendicularDirection() const { return m_perpendicularDirection; } + PDFReal getCutPlaneOpacity() const { return m_cutPlaneOpacity; } + QColor getCutPlaneColor() const { return m_cutPlaneColor; } + bool getIntersectionVisibility() const { return m_intersectionVisibility; } + QColor getIntersectionColor() const { return m_intersectionColor; } + bool getShowTransparent() const { return m_showTransparent; } + bool getSectionCapping() const { return m_sectionCapping; } + + /// Creates a new 3D cross section from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DCrossSection parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFPoint3D m_centerOfRotation{}; + PDFPoint3D m_rotationAngles{}; + Direction m_perpendicularDirection = Direction::X; + PDFReal m_cutPlaneOpacity = 0.5; + QColor m_cutPlaneColor = Qt::white; + bool m_intersectionVisibility = false; + QColor m_intersectionColor = Qt::green; + bool m_showTransparent = false; + bool m_sectionCapping = false; +}; + +/// 3D PDF lighting scheme +class PDF3DLightingScheme +{ +public: + explicit inline PDF3DLightingScheme() = default; + + enum class LightingScheme + { + Artwork, + None, + White, + Day, + Night, + Hard, + Primary, + Blue, + Red, + Cube, + CAD, + Headlamp + }; + + LightingScheme getLightingScheme() const { return m_scheme; } + + /// Creates a new 3D lighting scheme from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DLightingScheme parse(const PDFObjectStorage* storage, PDFObject object); + +private: + LightingScheme m_scheme = LightingScheme::Artwork; +}; + +/// 3D PDF background +class PDF3DBackground +{ +public: + explicit inline PDF3DBackground() = default; + + QColor getColor() const { return m_color; } + bool getEntireAnnotation() const { return m_entireAnnotation; } + + /// Creates a new 3D background from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DBackground parse(const PDFObjectStorage* storage, PDFObject object); + +private: + QColor m_color; + bool m_entireAnnotation = false; +}; + +/// 3D PDF projection +class PDF3DProjection +{ +public: + explicit inline PDF3DProjection() = default; + + enum class Projection + { + Orthographic, + Perspective + }; + + enum class ClippingStyle + { + Explicit, + Automatic + }; + + enum class ScaleMode + { + W, + H, + Min, + Max, + Absolute, + Value ///< This means projection scaling diameter is defined + }; + + Projection getProjection() const { return m_projection; } + ClippingStyle getClippingStyle() const { return m_clippingStyle; } + PDFReal getNearPlane() const { return m_near; } + PDFReal getFarPlane() const { return m_far; } + PDFReal getFieldOfViewAngle() const { return m_fieldOfViewAngle; } + PDFReal getProjectionScalingDiameter() const { return m_projectionScalingDiameter; } + ScaleMode getProjectionScaleMode() const { return m_projectionScaleMode; } + PDFReal getScaleFactor() const { return m_scaleFactor; } + ScaleMode getScaleMode() const { return m_scaleMode; } + + /// Creates a new 3D projection from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDF3DProjection parse(const PDFObjectStorage* storage, PDFObject object); + +private: + Projection m_projection = Projection::Orthographic; + ClippingStyle m_clippingStyle = ClippingStyle::Automatic; + PDFReal m_near = 0.0; + PDFReal m_far = qInf(); + PDFReal m_fieldOfViewAngle = 90.0; + PDFReal m_projectionScalingDiameter = 0.0; + ScaleMode m_projectionScaleMode = ScaleMode::W; + PDFReal m_scaleFactor = 1.0; + ScaleMode m_scaleMode = ScaleMode::Absolute; +}; + } // namespace pdf #endif // PDFMULTIMEDIA_H