diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 86bb977..3c8c85e 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -42,6 +42,7 @@ SOURCES += \ sources/pdffile.cpp \ sources/pdfitemmodels.cpp \ sources/pdfjbig2decoder.cpp \ + sources/pdfmultimedia.cpp \ sources/pdfobject.cpp \ sources/pdfoptionalcontent.cpp \ sources/pdfoutline.cpp \ @@ -78,6 +79,7 @@ HEADERS += \ sources/pdfitemmodels.h \ sources/pdfjbig2decoder.h \ sources/pdfmeshqualitysettings.h \ + sources/pdfmultimedia.h \ sources/pdfobject.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ diff --git a/PdfForQtLib/sources/pdfaction.cpp b/PdfForQtLib/sources/pdfaction.cpp index 7d31fe5..500433d 100644 --- a/PdfForQtLib/sources/pdfaction.cpp +++ b/PdfForQtLib/sources/pdfaction.cpp @@ -136,6 +136,111 @@ PDFActionPtr PDFAction::parseImpl(const PDFDocument* document, PDFObject object, { return PDFActionPtr(new PDFActionURI(loader.readStringFromDictionary(dictionary, "URI"), loader.readBooleanFromDictionary(dictionary, "IsMap", false))); } + else if (name == "Sound") + { + const PDFReal volume = loader.readNumberFromDictionary(dictionary, "Volume", 1.0); + const bool isSynchronous = loader.readBooleanFromDictionary(dictionary, "Synchronous", false); + const bool isRepeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false); + const bool isMix = loader.readBooleanFromDictionary(dictionary, "Mix", false); + return PDFActionPtr(new PDFActionSound(PDFSound::parse(document, dictionary->get("Sound")), volume, isSynchronous, isRepeat, isMix)); + } + else if (name == "Movie") + { + constexpr const std::array, 4> operations = { + std::pair{ "Play", PDFActionMovie::Operation::Play }, + std::pair{ "Stop", PDFActionMovie::Operation::Stop }, + std::pair{ "Pause", PDFActionMovie::Operation::Pause }, + std::pair{ "Resume", PDFActionMovie::Operation::Resume } + }; + + // Jakub Melka: parse the movie action + PDFObject annotationObject = dictionary->get("Annotation"); + PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference(); + QString title = loader.readTextStringFromDictionary(dictionary, "T", QString()); + PDFActionMovie::Operation operation = loader.readEnumByName(dictionary->get("Operation"), operations.cbegin(), operations.cend(), PDFActionMovie::Operation::Play); + + return PDFActionPtr(new PDFActionMovie(annotation, qMove(title), operation)); + } + else if (name == "Hide") + { + std::vector annotations; + std::vector fieldNames; + + const PDFObject& object = dictionary->get("T"); + if (object.isReference()) + { + annotations = { object.getReference() }; + } + else if (object.isString()) + { + fieldNames = { loader.readTextString(object, QString()) }; + } + else if (object.isArray()) + { + const PDFArray* items = object.getArray(); + for (size_t i = 0; i < items->getCount(); ++i) + { + const PDFObject& itemObject = items->getItem(i); + if (itemObject.isReference()) + { + annotations.push_back(itemObject.getReference()); + } + else if (itemObject.isString()) + { + fieldNames.push_back(loader.readTextString(itemObject, QString())); + } + } + } + + const bool hide = loader.readBooleanFromDictionary(dictionary, "H", true); + return PDFActionPtr(new PDFActionHide(qMove(annotations), qMove(fieldNames), hide)); + } + else if (name == "Named") + { + constexpr const std::array, 4> types = { + std::pair{ "NextPage", PDFActionNamed::NamedActionType::NextPage }, + std::pair{ "PrevPage", PDFActionNamed::NamedActionType::PrevPage }, + std::pair{ "FirstPage", PDFActionNamed::NamedActionType::FirstPage }, + std::pair{ "LastPage", PDFActionNamed::NamedActionType::LastPage } + }; + + QByteArray name = loader.readNameFromDictionary(dictionary, "N"); + PDFActionNamed::NamedActionType actionType = loader.readEnumByName(dictionary->get("N"), types.cbegin(), types.cend(), PDFActionNamed::NamedActionType::Custom); + return PDFActionPtr(new PDFActionNamed(actionType, qMove(name))); + } + else if (name == "SetOCGState") + { + const bool isRadioButtonsPreserved = loader.readBooleanFromDictionary(dictionary, "PreserveRB", true); + PDFActionSetOCGState::StateChangeItems items; + + PDFObject stateArrayObject = document->getObject(dictionary->get("State")); + if (stateArrayObject.isArray()) + { + constexpr const std::array, 3> types = { + std::pair{ "ON", PDFActionSetOCGState::SwitchType::ON }, + std::pair{ "OFF", PDFActionSetOCGState::SwitchType::OFF }, + std::pair{ "Toggle", PDFActionSetOCGState::SwitchType::Toggle } + }; + + PDFActionSetOCGState::SwitchType switchType = PDFActionSetOCGState::SwitchType::ON; + const PDFArray* stateArray = stateArrayObject.getArray(); + items.reserve(stateArray->getCount()); + for (size_t i = 0; i < stateArray->getCount(); ++i) + { + const PDFObject& item = stateArray->getItem(i); + if (item.isName()) + { + switchType = loader.readEnumByName(item, types.cbegin(), types.cend(), PDFActionSetOCGState::SwitchType::ON); + } + else if (item.isReference()) + { + items.emplace_back(switchType, item.getReference()); + } + } + } + + return PDFActionPtr(new PDFActionSetOCGState(qMove(items), isRadioButtonsPreserved)); + } return PDFActionPtr(); } diff --git a/PdfForQtLib/sources/pdfaction.h b/PdfForQtLib/sources/pdfaction.h index c9392ea..5bb71cd 100644 --- a/PdfForQtLib/sources/pdfaction.h +++ b/PdfForQtLib/sources/pdfaction.h @@ -21,6 +21,7 @@ #include "pdfglobal.h" #include "pdfobject.h" #include "pdffile.h" +#include "pdfmultimedia.h" #include @@ -40,7 +41,12 @@ enum class ActionType GoToE, Launch, Thread, - URI + URI, + Sound, + Movie, + Hide, + Named, + SetOCGState }; enum class DestinationType @@ -264,6 +270,149 @@ private: bool m_isMap; }; +class PDFActionSound : public PDFAction +{ +public: + explicit inline PDFActionSound(PDFSound sound, PDFReal volume, bool isSynchronous, bool isRepeat, bool isMix) : + m_sound(qMove(sound)), + m_volume(qMove(volume)), + m_isSynchronous(isSynchronous), + m_isRepeat(isRepeat), + m_isMix(isMix) + { + + } + + virtual ActionType getType() const override { return ActionType::Sound; } + + const PDFSound* getSound() const { return &m_sound; } + PDFReal getVolume() const { return m_volume; } + bool isSynchronous() const { return m_isSynchronous; } + bool isRepeat() const { return m_isRepeat; } + bool isMix() const { return m_isMix; } + +private: + PDFSound m_sound; + PDFReal m_volume; + bool m_isSynchronous; + bool m_isRepeat; + bool m_isMix; +}; + +class PDFActionMovie : public PDFAction +{ +public: + enum class Operation + { + Play, + Stop, + Pause, + Resume + }; + + explicit inline PDFActionMovie(PDFObjectReference annotation, QString title, Operation operation) : + m_annotation(qMove(annotation)), + m_title(qMove(title)), + m_operation(operation) + { + + } + + virtual ActionType getType() const override { return ActionType::Movie; } + + PDFObjectReference getAnnotation() const { return m_annotation; } + const QString& getTitle() const { return m_title; } + Operation getOperation() const { return m_operation; } + +private: + PDFObjectReference m_annotation; + QString m_title; + Operation m_operation; +}; + +class PDFActionHide : public PDFAction +{ +public: + explicit inline PDFActionHide(std::vector&& annotations, std::vector&& fieldNames, bool hide) : + m_annotations(qMove(annotations)), + m_fieldNames(qMove(fieldNames)), + m_hide(hide) + { + + } + + virtual ActionType getType() const override { return ActionType::Hide; } + + const std::vector& getAnnotations() const { return m_annotations; } + const std::vector& getFieldNames() const { return m_fieldNames; } + bool isHide() const { return m_hide; } + +private: + std::vector m_annotations; + std::vector m_fieldNames; + bool m_hide; +}; + +class PDFActionNamed : public PDFAction +{ +public: + enum class NamedActionType + { + Custom, + NextPage, + PrevPage, + FirstPage, + LastPage + }; + + explicit inline PDFActionNamed(NamedActionType namedActionType, QByteArray&& customNamedAction) : + m_namedActionType(namedActionType), + m_customNamedAction(qMove(customNamedAction)) + { + + } + + virtual ActionType getType() const override { return ActionType::Named; } + + NamedActionType getNamedActionType() const { return m_namedActionType; } + const QByteArray& getCustomNamedAction() const { return m_customNamedAction; } + +private: + NamedActionType m_namedActionType; + QByteArray m_customNamedAction; +}; + +class PDFActionSetOCGState : public PDFAction +{ +public: + + enum class SwitchType + { + ON, + OFF, + Toggle + }; + + using StateChangeItem = std::pair; + using StateChangeItems = std::vector; + + explicit inline PDFActionSetOCGState(StateChangeItems&& stateChangeItems, bool isRadioButtonsPreserved) : + m_items(qMove(stateChangeItems)), + m_isRadioButtonsPreserved(isRadioButtonsPreserved) + { + + } + + virtual ActionType getType() const override { return ActionType::SetOCGState; } + + const StateChangeItems& getStateChangeItems() const { return m_items; } + bool isRadioButtonsPreserved() const { return m_isRadioButtonsPreserved; } + +private: + StateChangeItems m_items; + bool m_isRadioButtonsPreserved; +}; + } // namespace pdf #endif // PDFACTION_H diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index 6ff70cb..904fe37 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -547,4 +547,46 @@ QByteArray PDFDocumentDataLoaderDecorator::readStringFromDictionary(const PDFDic return QByteArray(); } +std::vector PDFDocumentDataLoaderDecorator::readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readStringArray(dictionary->get(key)); + } + + return std::vector(); +} + +std::vector PDFDocumentDataLoaderDecorator::readStringArray(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_document->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFObject& stringObject = array->getItem(i); + if (stringObject.isString()) + { + result.push_back(stringObject.getString()); + } + else + { + result.clear(); + break; + } + } + + // We assume, that RVO (return value optimization) will not work for this function + // (multiple return points). + return std::move(result); + } + + return std::vector(); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 4e5ab68..6aea9e0 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -139,7 +139,7 @@ public: Enum readEnumByName(const PDFObject& object, Iterator begin, Iterator end, Enum defaultValue) const { const PDFObject& dereferencedObject = m_document->getObject(object); - if (dereferencedObject.isName()) + if (dereferencedObject.isName() || dereferencedObject.isString()) { QByteArray name = dereferencedObject.getString(); @@ -258,6 +258,11 @@ public: /// \param object Object containing array of references std::vector readNameArray(const PDFObject& object) const; + /// Reads string array. Reads all values. If error occurs, + /// then empty array is returned. + /// \param object Object containing array of references + std::vector readStringArray(const PDFObject& object) const; + /// Reads name array from dictionary. Reads all values. If error occurs, /// then empty array is returned. /// \param dictionary Dictionary containing desired data @@ -280,6 +285,12 @@ public: /// \param key Entry key QByteArray readStringFromDictionary(const PDFDictionary* dictionary, const char* key); + /// Reads string array from dictionary. Reads all values. If error occurs, + /// then empty array is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::vector readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; + private: const PDFDocument* m_document; }; @@ -326,6 +337,10 @@ public: /// is returned (no exception is thrown). const PDFObject& getObject(const PDFObject& object) const; + /// Returns dictionary from an object. If object is not a dictionary, + /// then nullptr is returned (no exception is thrown). + const PDFDictionary* getDictionaryFromObject(const PDFObject& object) const; + /// Returns object by reference. If dereference attempt fails, then null object /// is returned (no exception is thrown). const PDFObject& getObjectByReference(PDFObjectReference reference) const; @@ -383,6 +398,22 @@ const PDFObject& PDFDocument::getObject(const PDFObject& object) const return object; } +inline +const PDFDictionary* PDFDocument::getDictionaryFromObject(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = getObject(object); + if (dereferencedObject.isDictionary()) + { + return dereferencedObject.getDictionary(); + } + else if (dereferencedObject.isStream()) + { + return dereferencedObject.getStream()->getDictionary(); + } + + return nullptr; +} + inline const PDFObject& PDFDocument::getObjectByReference(PDFObjectReference reference) const { diff --git a/PdfForQtLib/sources/pdfmultimedia.cpp b/PdfForQtLib/sources/pdfmultimedia.cpp new file mode 100644 index 0000000..48cc5ae --- /dev/null +++ b/PdfForQtLib/sources/pdfmultimedia.cpp @@ -0,0 +1,540 @@ +// Copyright (C) 2019 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt 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 +// (at your option) any later version. +// +// PdfForQt 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 PDFForQt. If not, see . + +#include "pdfmultimedia.h" +#include "pdfdocument.h" + +namespace pdf +{ + +PDFSound PDFSound::parse(const PDFDocument* document, PDFObject object) +{ + PDFSound result; + + object = document->getObject(object); + if (object.isStream()) + { + const PDFStream* stream = object.getStream(); + const PDFDictionary* dictionary = stream->getDictionary(); + + constexpr const std::array, 4> formats = { + std::pair{ "Raw", Format::Raw }, + std::pair{ "Signed", Format::Signed }, + std::pair{ "muLaw", Format::muLaw }, + std::pair{ "ALaw", Format::ALaw } + }; + + // Jakub Melka: parse the sound without exceptions + PDFDocumentDataLoaderDecorator loader(document); + result.m_fileSpecification = PDFFileSpecification::parse(document, dictionary->get("F")); + result.m_samplingRate = loader.readNumberFromDictionary(dictionary, "R", 0.0); + result.m_channels = loader.readIntegerFromDictionary(dictionary, "C", 1); + result.m_bitsPerSample = loader.readIntegerFromDictionary(dictionary, "B", 8); + result.m_format = loader.readEnumByName(dictionary->get("E"), formats.cbegin(), formats.cend(), Format::Raw); + result.m_soundCompression = loader.readNameFromDictionary(dictionary, "CO"); + result.m_soundCompressionParameters = document->getObject(dictionary->get("CP")); + result.m_streamObject = object; + } + + return result; +} + +PDFRendition PDFRendition::parse(const PDFDocument* document, PDFObject object) +{ + PDFRendition result; + object = document->getObject(object); + const PDFDictionary* renditionDictionary = nullptr; + if (object.isDictionary()) + { + renditionDictionary = object.getDictionary(); + } + else if (object.isStream()) + { + renditionDictionary = object.getStream()->getDictionary(); + } + + if (renditionDictionary) + { + constexpr const std::array, 2> types = { + std::pair{ "MR", Type::Media }, + std::pair{ "SR", Type::Selector } + }; + + PDFDocumentDataLoaderDecorator loader(document); + result.m_type = loader.readEnumByName(renditionDictionary->get("S"), types.cbegin(), types.cend(), Type::Invalid); + result.m_name = loader.readTextStringFromDictionary(renditionDictionary, "N", QString()); + + auto readMediaCriteria = [renditionDictionary, document](const char* entry) + { + PDFObject dictionaryObject = document->getObject(renditionDictionary->get(entry)); + if (dictionaryObject.isDictionary()) + { + const PDFDictionary* dictionary = dictionaryObject.getDictionary(); + return PDFMediaCriteria::parse(document, dictionary->get("C")); + } + + return PDFMediaCriteria(); + }; + + result.m_mustHonored = readMediaCriteria("MH"); + result.m_bestEffort = readMediaCriteria("BE"); + + switch (result.m_type) + { + case Type::Media: + { + MediaRenditionData data; + data.clip = PDFMediaClip::parse(document, renditionDictionary->get("C")); + data.playParameters = PDFMediaPlayParameters::parse(document, renditionDictionary->get("P")); + data.screenParameters = PDFMediaScreenParameters::parse(document, renditionDictionary->get("SP")); + + result.m_data = qMove(data); + break; + } + + case Type::Selector: + { + result.m_data = SelectorRenditionData{ renditionDictionary->get("R") }; + break; + } + + default: + break; + } + } + + return result; +} + +PDFMediaMinimumBitDepth PDFMediaMinimumBitDepth::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + return PDFMediaMinimumBitDepth(loader.readIntegerFromDictionary(dictionary, "V", -1), loader.readIntegerFromDictionary(dictionary, "M", 0)); + } + + return PDFMediaMinimumBitDepth(-1, -1); +} + +PDFMediaMinimumScreenSize PDFMediaMinimumScreenSize::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + std::vector values = loader.readIntegerArrayFromDictionary(dictionary, "V"); + if (values.size() == 2) + { + return PDFMediaMinimumScreenSize(values[0], values[1], loader.readIntegerFromDictionary(dictionary, "M", 0)); + } + } + + return PDFMediaMinimumScreenSize(-1, -1, -1); +} + +PDFMediaSoftwareIdentifier PDFMediaSoftwareIdentifier::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + return PDFMediaSoftwareIdentifier(loader.readTextStringFromDictionary(dictionary, "U", QString()).toLatin1(), + loader.readIntegerArrayFromDictionary(dictionary, "L"), + loader.readIntegerArrayFromDictionary(dictionary, "H"), + loader.readBooleanFromDictionary(dictionary, "LI", true), + loader.readBooleanFromDictionary(dictionary, "HI", true), + loader.readStringArrayFromDictionary(dictionary, "OS")); + } + + return PDFMediaSoftwareIdentifier(QByteArray(), { }, { }, true, true, { }); +} + +PDFMediaCriteria PDFMediaCriteria::parse(const PDFDocument* document, PDFObject object) +{ + PDFMediaCriteria criteria; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + auto readBoolean = [&loader, dictionary](const char* name, std::optional& value) + { + if (dictionary->hasKey(name)) + { + value = loader.readBooleanFromDictionary(dictionary, name, false); + } + }; + + readBoolean("A", criteria.m_audioDescriptions); + readBoolean("C", criteria.m_textCaptions); + readBoolean("O", criteria.m_audioOverdubs); + readBoolean("S", criteria.m_subtitles); + if (dictionary->hasKey("R")) + { + criteria.m_bitrate = loader.readIntegerFromDictionary(dictionary, "R", 0); + } + if (dictionary->hasKey("D")) + { + criteria.m_minimumBitDepth = PDFMediaMinimumBitDepth::parse(document, dictionary->get("D")); + } + if (dictionary->hasKey("Z")) + { + criteria.m_minimumScreenSize = PDFMediaMinimumScreenSize::parse(document, dictionary->get("Z")); + } + if (dictionary->hasKey("V")) + { + const PDFObject& viewerObject = document->getObject(dictionary->get("V")); + if (viewerObject.isArray()) + { + std::vector viewers; + const PDFArray* viewersArray = viewerObject.getArray(); + viewers.reserve(viewersArray->getCount()); + for (size_t i = 0; i < viewersArray->getCount(); ++i) + { + viewers.emplace_back(PDFMediaSoftwareIdentifier::parse(document, viewersArray->getItem(i))); + } + criteria.m_viewers = qMove(viewers); + } + } + std::vector pdfVersions = loader.readNameArrayFromDictionary(dictionary, "P"); + if (pdfVersions.size() > 0) + { + criteria.m_minimumPdfVersion = qMove(pdfVersions[0]); + if (pdfVersions.size() > 1) + { + criteria.m_maximumPdfVersion = qMove(pdfVersions[1]); + } + } + if (dictionary->hasKey("L")) + { + criteria.m_languages = loader.readStringArrayFromDictionary(dictionary, "L"); + } + } + + return criteria; +} + +PDFMediaPermissions PDFMediaPermissions::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + constexpr const std::array, 4> types = { + std::pair{ "TEMPNEVER", Permission::Never }, + std::pair{ "TEMPEXTRACT", Permission::Extract }, + std::pair{ "TEMPACCESS", Permission::Access }, + std::pair{ "TEMPALWAYS", Permission::Always } + }; + + return PDFMediaPermissions(loader.readEnumByName(dictionary->get("TF"), types.cbegin(), types.cend(), Permission::Never)); + } + + return PDFMediaPermissions(Permission::Never); +} + +PDFMediaPlayers PDFMediaPlayers::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + auto readPlayers = [document, dictionary](const char* key) + { + std::vector result; + + const PDFObject& playersArrayObject = document->getObject(dictionary->get(key)); + if (playersArrayObject.isArray()) + { + const PDFArray* playersArray = playersArrayObject.getArray(); + result.reserve(playersArray->getCount()); + + for (size_t i = 0; i < playersArray->getCount(); ++i) + { + result.emplace_back(PDFMediaPlayer::parse(document, playersArray->getItem(i))); + } + } + + return result; + }; + + return PDFMediaPlayers(readPlayers("MU"), readPlayers("A"), readPlayers("NU")); + } + + return PDFMediaPlayers({ }, { }, { }); +} + +PDFMediaPlayer PDFMediaPlayer::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + return PDFMediaPlayer(PDFMediaSoftwareIdentifier::parse(document, dictionary->get("PID"))); + } + return PDFMediaPlayer(PDFMediaSoftwareIdentifier(QByteArray(), { }, { }, true, true, { })); +} + +PDFMediaOffset PDFMediaOffset::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + QByteArray S = loader.readNameFromDictionary(dictionary, "S"); + if (S == "T") + { + if (const PDFDictionary* timespanDictionary = document->getDictionaryFromObject(dictionary->get("T"))) + { + return PDFMediaOffset(Type::Time, TimeData{ loader.readIntegerFromDictionary(timespanDictionary, "V", 0) }); + } + } + else if (S == "F") + { + return PDFMediaOffset(Type::Frame, FrameData{ loader.readIntegerFromDictionary(dictionary, "F", 0) }); + } + else if (S == "M") + { + return PDFMediaOffset(Type::Marker, MarkerData{ loader.readTextStringFromDictionary(dictionary, "M", QString()) }); + } + } + + return PDFMediaOffset(Type::Invalid, std::monostate()); +} + +PDFMediaClip PDFMediaClip::parse(const PDFDocument* document, PDFObject object) +{ + MediaClipData clipData; + std::vector sections; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + std::set usedReferences; + while (dictionary) + { + QByteArray type = loader.readNameFromDictionary(dictionary, "S"); + if (type == "MCD") + { + clipData.name = loader.readTextStringFromDictionary(dictionary, "N", QString()); + + PDFObject fileSpecificationOrStreamObject = document->getObject(dictionary->get("D")); + if (fileSpecificationOrStreamObject.isStream()) + { + clipData.dataStream = fileSpecificationOrStreamObject; + } + else + { + clipData.fileSpecification = PDFFileSpecification::parse(document, fileSpecificationOrStreamObject); + } + clipData.contentType = loader.readStringFromDictionary(dictionary, "CT"); + clipData.permissions = PDFMediaPermissions::parse(document, dictionary->get("P")); + clipData.alternateTextDescriptions = PDFMediaMultiLanguageTexts::parse(document, dictionary->get("Alt")); + clipData.players = PDFMediaPlayers::parse(document, dictionary->get("PL")); + + auto getBaseUrl = [&loader, document, dictionary](const char* name) + { + if (const PDFDictionary* subdictionary = document->getDictionaryFromObject(dictionary->get(name))) + { + return loader.readStringFromDictionary(subdictionary, "BU"); + } + + return QByteArray(); + }; + clipData.m_baseUrlMustHonored = getBaseUrl("MH"); + clipData.m_baseUrlBestEffort = getBaseUrl("BE"); + break; + } + else if (type == "MCS") + { + MediaSectionData sectionData; + sectionData.name = loader.readTextStringFromDictionary(dictionary, "N", QString()); + sectionData.alternateTextDescriptions = PDFMediaMultiLanguageTexts::parse(document, dictionary->get("Alt")); + + auto readMediaSectionBeginEnd = [document, dictionary](const char* name) + { + MediaSectionBeginEnd result; + + if (const PDFDictionary* subdictionary = document->getDictionaryFromObject(dictionary->get(name))) + { + result.offsetBegin = PDFMediaOffset::parse(document, subdictionary->get("B")); + result.offsetEnd = PDFMediaOffset::parse(document, subdictionary->get("E")); + } + + return result; + }; + sectionData.m_mustHonored = readMediaSectionBeginEnd("MH"); + sectionData.m_bestEffort = readMediaSectionBeginEnd("BE"); + + // Jakub Melka: parse next item in linked list + PDFObject next = dictionary->get("D"); + if (next.isReference()) + { + if (usedReferences.count(next.getReference())) + { + break; + } + usedReferences.insert(next.getReference()); + } + dictionary = document->getDictionaryFromObject(next); + continue; + } + + dictionary = nullptr; + } + } + + return PDFMediaClip(qMove(clipData), qMove(sections)); +} + +PDFMediaMultiLanguageTexts PDFMediaMultiLanguageTexts::parse(const PDFDocument* document, PDFObject object) +{ + PDFMediaMultiLanguageTexts texts; + + object = document->getObject(object); + if (object.isArray()) + { + const PDFArray* array = object.getArray(); + if (array->getCount() % 2 == 0) + { + PDFDocumentDataLoaderDecorator loader(document); + const size_t pairs = array->getCount() / 2; + for (size_t i = 0; i < pairs; ++i) + { + const PDFObject& languageName = document->getObject(array->getItem(2 * i)); + const PDFObject& text = array->getItem(2 * i + 1); + + if (languageName.isString()) + { + texts.texts[languageName.getString()] = loader.readTextString(text, QString()); + } + } + } + } + + return texts; +} + +PDFMediaPlayParameters PDFMediaPlayParameters::parse(const PDFDocument* document, PDFObject object) +{ + PDFMediaPlayParameters result; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + result.m_players = PDFMediaPlayers::parse(document, dictionary->get("PL")); + + auto getPlayParameters = [document, dictionary](const char* name) + { + PlayParameters parameters; + + if (const PDFDictionary* subdictionary = document->getDictionaryFromObject(dictionary->get(name))) + { + PDFDocumentDataLoaderDecorator loader(document); + parameters.volume = loader.readIntegerFromDictionary(subdictionary, "V", 100); + parameters.controllerUserInterface = loader.readBooleanFromDictionary(subdictionary, "C", false); + parameters.fitMode = static_cast(loader.readIntegerFromDictionary(subdictionary, "F", 5)); + parameters.playAutomatically = loader.readBooleanFromDictionary(subdictionary, "A", true); + parameters.repeat = loader.readNumberFromDictionary(dictionary, "RC", 1.0); + + if (const PDFDictionary* durationDictionary = document->getDictionaryFromObject(subdictionary->get("D"))) + { + constexpr const std::array, 3> durations = { + std::pair{ "I", Duration::Intrinsic }, + std::pair{ "F", Duration::Infinity }, + std::pair{ "T", Duration::Seconds } + }; + parameters.duration = loader.readEnumByName(durationDictionary->get("S"), durations.cbegin(), durations.cend(), Duration::Intrinsic); + + if (const PDFDictionary* timeDictionary = document->getDictionaryFromObject(durationDictionary->get("T"))) + { + parameters.durationSeconds = loader.readNumberFromDictionary(timeDictionary, "V", 0.0); + } + } + } + + return parameters; + }; + result.m_mustHonored = getPlayParameters("MH"); + result.m_bestEffort = getPlayParameters("BE"); + } + + return result; +} + +PDFMediaScreenParameters PDFMediaScreenParameters::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + auto getScreenParameters = [document, dictionary](const char* name) + { + ScreenParameters result; + if (const PDFDictionary* screenDictionary = document->getDictionaryFromObject(dictionary->get(name))) + { + PDFDocumentDataLoaderDecorator loader(document); + result.windowType = static_cast(loader.readIntegerFromDictionary(screenDictionary, "W", 3)); + result.opacity = loader.readNumberFromDictionary(screenDictionary, "O", 1.0); + result.monitorSpecification = loader.readIntegerFromDictionary(screenDictionary, "M", 0); + std::vector rgb = loader.readNumberArrayFromDictionary(screenDictionary, "B", { 1.0, 1.0, 1.0 }); + rgb.resize(3, 1.0); + result.backgroundColor.setRgbF(rgb[0], rgb[1], rgb[2]); + + if (const PDFDictionary* floatWindowDictionary = document->getDictionaryFromObject(screenDictionary->get("F"))) + { + std::vector sizeArray = loader.readIntegerArrayFromDictionary(floatWindowDictionary, "D"); + sizeArray.resize(2, 0); + result.floatingWindowSize = QSize(sizeArray[0], sizeArray[1]); + result.floatingWindowReference = static_cast(loader.readIntegerFromDictionary(floatWindowDictionary, "RT", 0)); + result.floatingWindowOffscreenMode = static_cast(loader.readIntegerFromDictionary(floatWindowDictionary, "O", 1)); + result.floatingWindowHasTitleBar = loader.readBooleanFromDictionary(floatWindowDictionary, "T", true); + result.floatingWindowCloseable = loader.readBooleanFromDictionary(floatWindowDictionary, "UC", true); + result.floatingWindowResizeMode = static_cast(loader.readIntegerFromDictionary(floatWindowDictionary, "R", 0)); + result.floatingWindowTitle = PDFMediaMultiLanguageTexts::parse(document, floatWindowDictionary->get("TT")); + switch (loader.readIntegerFromDictionary(floatWindowDictionary, "P", 4)) + { + case 0: + result.floatingWindowAlignment = Qt::AlignTop | Qt::AlignLeft; + break; + case 1: + result.floatingWindowAlignment = Qt::AlignTop | Qt::AlignHCenter; + break; + case 2: + result.floatingWindowAlignment = Qt::AlignTop | Qt::AlignRight; + break; + case 3: + result.floatingWindowAlignment = Qt::AlignVCenter | Qt::AlignLeft; + break; + case 4: + default: + result.floatingWindowAlignment = Qt::AlignCenter; + break; + case 5: + result.floatingWindowAlignment = Qt::AlignVCenter | Qt::AlignRight; + break; + case 6: + result.floatingWindowAlignment = Qt::AlignBottom | Qt::AlignLeft; + break; + case 7: + result.floatingWindowAlignment = Qt::AlignBottom | Qt::AlignHCenter; + break; + case 8: + result.floatingWindowAlignment = Qt::AlignBottom | Qt::AlignRight; + break; + } + } + } + + return result; + }; + return PDFMediaScreenParameters(getScreenParameters("MH"), getScreenParameters("BE")); + } + + return PDFMediaScreenParameters(); +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfmultimedia.h b/PdfForQtLib/sources/pdfmultimedia.h new file mode 100644 index 0000000..e1cce90 --- /dev/null +++ b/PdfForQtLib/sources/pdfmultimedia.h @@ -0,0 +1,537 @@ +// Copyright (C) 2019 Jakub Melka +// +// This file is part of PdfForQt. +// +// PdfForQt 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 +// (at your option) any later version. +// +// PdfForQt 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 PDFForQt. If not, see . + +#ifndef PDFMULTIMEDIA_H +#define PDFMULTIMEDIA_H + +#include "pdfobject.h" +#include "pdffile.h" + +#include + +#include +#include + +namespace pdf +{ +class PDFDocument; + +struct PDFMediaMultiLanguageTexts +{ + static PDFMediaMultiLanguageTexts parse(const PDFDocument* document, PDFObject object); + + std::map texts; +}; + +class PDFMediaOffset +{ +public: + + enum class Type + { + Invalid, + Time, + Frame, + Marker + }; + + struct TimeData + { + PDFInteger seconds = 0; + }; + + struct FrameData + { + PDFInteger frame = 0; + }; + + struct MarkerData + { + QString namedOffset; + }; + + explicit inline PDFMediaOffset() : + m_type(Type::Invalid) + { + + } + + template + explicit inline PDFMediaOffset(Type type, Data&& data) : + m_type(type), + m_data(qMove(data)) + { + + } + + static PDFMediaOffset parse(const PDFDocument* document, PDFObject object); + + const TimeData* getTimeData() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } + const FrameData* getFrameData() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } + const MarkerData* getMarkerData() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } + +private: + Type m_type; + std::variant m_data; +}; + +class PDFMediaSoftwareIdentifier +{ +public: + explicit inline PDFMediaSoftwareIdentifier(QByteArray&& software, std::vector&& lowVersion, std::vector&& highVersion, + bool lowVersionInclusive, bool highVersionInclusive, std::vector&& languages) : + m_software(qMove(software)), + m_lowVersion(qMove(lowVersion)), + m_highVersion(qMove(highVersion)), + m_lowVersionInclusive(lowVersionInclusive), + m_highVersionInclusive(highVersionInclusive), + m_languages(qMove(languages)) + { + + } + + static PDFMediaSoftwareIdentifier parse(const PDFDocument* document, PDFObject object); + + const QByteArray& getSoftware() const { return m_software; } + const std::vector& getLowVersion() const { return m_lowVersion; } + const std::vector& getHighVersion() const { return m_highVersion; } + bool isLowVersionInclusive() const { return m_lowVersionInclusive; } + bool isHighVersionInclusive() const { return m_highVersionInclusive; } + const std::vector& getLanguages() const { return m_languages; } + +private: + QByteArray m_software; + std::vector m_lowVersion; + std::vector m_highVersion; + bool m_lowVersionInclusive; + bool m_highVersionInclusive; + std::vector m_languages; +}; + +class PDFMediaPlayer +{ +public: + explicit inline PDFMediaPlayer(PDFMediaSoftwareIdentifier&& softwareIdentifier) : + m_softwareIdentifier(qMove(softwareIdentifier)) + { + + } + + static PDFMediaPlayer parse(const PDFDocument* document, PDFObject object); + + const PDFMediaSoftwareIdentifier* getSoftwareIdentifier() const { return &m_softwareIdentifier; } + +private: + PDFMediaSoftwareIdentifier m_softwareIdentifier; +}; + +class PDFMediaPlayers +{ +public: + explicit inline PDFMediaPlayers() = default; + + explicit inline PDFMediaPlayers(std::vector&& playersMustUsed, + std::vector&& playersAlternate, + std::vector&& playersNeverUsed) : + m_playersMustUsed(qMove(playersMustUsed)), + m_playersNeverUsed(qMove(playersNeverUsed)), + m_playersAlternate(qMove(playersAlternate)) + { + + } + + static PDFMediaPlayers parse(const PDFDocument* document, PDFObject object); + + const std::vector& getPlayersMustUsed() const { return m_playersMustUsed; } + const std::vector& getPlayersAlternate() const { return m_playersAlternate; } + const std::vector& getPlayersNeverUsed() const { return m_playersNeverUsed; } + +private: + std::vector m_playersMustUsed; + std::vector m_playersAlternate; + std::vector m_playersNeverUsed; +}; + +class PDFMediaPermissions +{ +public: + + /// Are we allowed to save temporary file to play rendition? + enum class Permission + { + Never, + Extract, + Access, + Always + }; + + explicit inline PDFMediaPermissions() : + m_permission(Permission::Never) + { + + } + + explicit inline PDFMediaPermissions(Permission permission) : + m_permission(permission) + { + + } + + static PDFMediaPermissions parse(const PDFDocument* document, PDFObject object); + + Permission getPermission() const { return m_permission; } + +private: + Permission m_permission; +}; + +class PDFMediaPlayParameters +{ +public: + explicit inline PDFMediaPlayParameters() = default; + + enum class FitMode + { + Meet, + Slice, + Fill, + Scroll, + Hidden, + Default + }; + + enum class Duration + { + Intrinsic, + Infinity, + Seconds + }; + + struct PlayParameters + { + PDFInteger volume = 100; + bool controllerUserInterface = false; + FitMode fitMode = FitMode::Default; + bool playAutomatically = true; + PDFReal repeat = 1.0; + Duration duration = Duration::Intrinsic; + PDFReal durationSeconds = 0.0; + }; + + static PDFMediaPlayParameters parse(const PDFDocument* document, PDFObject object); + + const PDFMediaPlayers* getPlayers() const { return &m_players; } + const PlayParameters* getPlayParametersMustHonored() const { return &m_mustHonored; } + const PlayParameters* getPlayParametersBestEffort() const { return &m_bestEffort; } + +private: + PDFMediaPlayers m_players; + PlayParameters m_mustHonored; + PlayParameters m_bestEffort; +}; + +class PDFMediaScreenParameters +{ +public: + + enum class WindowType + { + Floating, + FullScreen, + Hidden, + ScreenAnnotation + }; + + enum class WindowRelativeTo + { + DocumentWindow, + ApplicationWindow, + VirtualDesktop, + Monitor + }; + + enum class OffscreenMode + { + NoAction, + MoveOnScreen, + NonViable + }; + + enum class ResizeMode + { + Fixed, + ResizableKeepAspectRatio, + Resizeble + }; + + struct ScreenParameters + { + WindowType windowType = WindowType::ScreenAnnotation; + QColor backgroundColor = QColor(Qt::white); + PDFReal opacity = 1.0; + PDFInteger monitorSpecification = 0; + + QSize floatingWindowSize; + WindowRelativeTo floatingWindowReference = WindowRelativeTo::DocumentWindow; + Qt::Alignment floatingWindowAlignment = Qt::AlignCenter; + OffscreenMode floatingWindowOffscreenMode = OffscreenMode::MoveOnScreen; + bool floatingWindowHasTitleBar = true; + bool floatingWindowCloseable = true; + ResizeMode floatingWindowResizeMode = ResizeMode::Fixed; + PDFMediaMultiLanguageTexts floatingWindowTitle; + }; + + explicit inline PDFMediaScreenParameters() = default; + explicit inline PDFMediaScreenParameters(ScreenParameters&& mustHonored, ScreenParameters&& bestEffort) : + m_mustHonored(qMove(mustHonored)), + m_bestEffort(qMove(bestEffort)) + { + + } + + static PDFMediaScreenParameters parse(const PDFDocument* document, PDFObject object); + + const ScreenParameters* getScreenParametersMustHonored() const { return &m_mustHonored; } + const ScreenParameters* getScreenParametersBestEffort() const { return &m_bestEffort; } + +private: + ScreenParameters m_mustHonored; + ScreenParameters m_bestEffort; +}; + +class PDFMediaClip +{ +public: + struct MediaClipData + { + QString name; + PDFFileSpecification fileSpecification; + PDFObject dataStream; + QByteArray contentType; + PDFMediaPermissions permissions; + PDFMediaMultiLanguageTexts alternateTextDescriptions; + PDFMediaPlayers players; + QByteArray m_baseUrlMustHonored; + QByteArray m_baseUrlBestEffort; + }; + + struct MediaSectionBeginEnd + { + PDFMediaOffset offsetBegin; + PDFMediaOffset offsetEnd; + }; + + struct MediaSectionData + { + QString name; + PDFMediaMultiLanguageTexts alternateTextDescriptions; + MediaSectionBeginEnd m_mustHonored; + MediaSectionBeginEnd m_bestEffort; + }; + + explicit inline PDFMediaClip() = default; + + explicit inline PDFMediaClip(MediaClipData&& mediaClipData, std::vector&& sections) : + m_mediaClipData(qMove(mediaClipData)), + m_sections(qMove(sections)) + { + + } + + static PDFMediaClip parse(const PDFDocument* document, PDFObject object); + + const MediaClipData& getMediaClipData() const { return m_mediaClipData; } + const std::vector& getClipSections() const { return m_sections; } + +private: + MediaClipData m_mediaClipData; + std::vector m_sections; +}; + +class PDFMediaMinimumBitDepth +{ +public: + explicit inline PDFMediaMinimumBitDepth(PDFInteger screenMinimumBitDepth, PDFInteger monitorSpecifier) : + m_screenMinimumBitDepth(screenMinimumBitDepth), + m_monitorSpecifier(monitorSpecifier) + { + + } + + static PDFMediaMinimumBitDepth parse(const PDFDocument* document, PDFObject object); + + PDFInteger getScreenMinimumBitDepth() const { return m_screenMinimumBitDepth; } + PDFInteger getMonitorSpecifier() const { return m_monitorSpecifier; } + +private: + PDFInteger m_screenMinimumBitDepth; + PDFInteger m_monitorSpecifier; +}; + +class PDFMediaMinimumScreenSize +{ +public: + explicit inline PDFMediaMinimumScreenSize(PDFInteger minimumWidth, PDFInteger minimumHeight, PDFInteger monitorSpecifier) : + m_minimumWidth(minimumWidth), + m_minimumHeight(minimumHeight), + m_monitorSpecifier(monitorSpecifier) + { + + } + + static PDFMediaMinimumScreenSize parse(const PDFDocument* document, PDFObject object); + +private: + PDFInteger m_minimumWidth; + PDFInteger m_minimumHeight; + PDFInteger m_monitorSpecifier; +}; + +/// Media critera object (see PDF 1.7 reference, chapter 9.1.2). Some values are optional, +/// so they are implemented using std::optional. Always call "has" functions before +/// accessing the getters. +class PDFMediaCriteria +{ +public: + explicit inline PDFMediaCriteria() = default; + + static PDFMediaCriteria parse(const PDFDocument* document, PDFObject object); + + bool hasAudioDescriptions() const { return m_audioDescriptions.has_value(); } + bool hasTextCaptions() const { return m_textCaptions.has_value(); } + bool hasAudioOverdubs() const { return m_audioOverdubs.has_value(); } + bool hasSubtitles() const { return m_subtitles.has_value(); } + bool hasBitrate() const { return m_bitrate.has_value(); } + bool hasMinimumBitDepth() const { return m_minimumBitDepth.has_value(); } + bool hasMinimumScreenSize() const { return m_minimumScreenSize.has_value(); } + bool hasViewers() const { return m_viewers.has_value(); } + bool hasMinimumPdfVersion() const { return m_minimumPdfVersion.has_value(); } + bool hasMaximumPdfVersion() const { return m_maximumPdfVersion.has_value(); } + bool hasLanguages() const { return m_languages.has_value(); } + + bool getAudioDescriptions() const { return m_audioDescriptions.value(); } + bool getTextCaptions() const { return m_textCaptions.value(); } + bool getAudioOverdubs() const { return m_audioOverdubs.value(); } + bool getSubtitles() const { return m_subtitles.value(); } + PDFInteger getBitrate() const { return m_bitrate.value(); } + const PDFMediaMinimumBitDepth& getMinimumBitDepth() const { return m_minimumBitDepth.value(); } + const PDFMediaMinimumScreenSize& getMinimumScreenSize() const { return m_minimumScreenSize.value(); } + const std::vector& getViewers() const { return m_viewers.value(); } + const QByteArray& getMinimumPdfVersion() const { return m_minimumPdfVersion.value(); } + const QByteArray& getMaximumPdfVersion() const { return m_maximumPdfVersion.value(); } + const std::vector& getLanguages() const { return m_languages.value(); } + +private: + std::optional m_audioDescriptions; + std::optional m_textCaptions; + std::optional m_audioOverdubs; + std::optional m_subtitles; + std::optional m_bitrate; + std::optional m_minimumBitDepth; + std::optional m_minimumScreenSize; + std::optional> m_viewers; + std::optional m_minimumPdfVersion; + std::optional m_maximumPdfVersion; + std::optional> m_languages; +}; + +/// Rendition object +class PDFRendition +{ +public: + + enum class Type + { + Invalid, + Media, + Selector + }; + + struct MediaRenditionData + { + PDFMediaClip clip; + PDFMediaPlayParameters playParameters; + PDFMediaScreenParameters screenParameters; + }; + + struct SelectorRenditionData + { + PDFObject renditions; + }; + + static PDFRendition parse(const PDFDocument* document, PDFObject object); + + Type getType() const { return m_type; } + const QString& getName() const { return m_name; } + const PDFMediaCriteria* getMediaCriteriaMustHonored() const { return &m_mustHonored; } + const PDFMediaCriteria* getMediaCriteriaBestEffort() const { return &m_bestEffort; } + const MediaRenditionData* getMediaRenditionData() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } + const SelectorRenditionData* getSelectorRenditionData() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } + +private: + Type m_type = Type::Invalid; + QString m_name; + + PDFMediaCriteria m_mustHonored; + PDFMediaCriteria m_bestEffort; + std::variant m_data; +}; + +/// Sound object, see chapter 9.2 in PDF 1.7 reference +class PDFSound +{ +public: + explicit inline PDFSound() = default; + + enum class Format + { + Raw, + Signed, + muLaw, + ALaw + }; + + const PDFFileSpecification* getFileSpecification() const { return &m_fileSpecification; } + PDFReal getSamplingRate() const { return m_samplingRate; } + PDFInteger getChannels() const { return m_channels; } + PDFInteger getBitsPerSample() const { return m_bitsPerSample; } + Format getFormat() const { return m_format; } + const QByteArray& getSoundCompression() { return m_soundCompression; } + const PDFObject& getSoundCompressionParameters() const { return m_soundCompressionParameters; } + const PDFStream* getStream() const { return m_streamObject.isStream() ? m_streamObject.getStream() : nullptr; } + + /// If this function returns true, sound is valid + bool isValid() const { return getStream(); } + + /// Creates a new sound from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDFSound parse(const PDFDocument* document, PDFObject object); + +private: + PDFFileSpecification m_fileSpecification; + PDFReal m_samplingRate = 0.0; + PDFInteger m_channels = 0; + PDFInteger m_bitsPerSample = 0; + Format m_format = Format::Raw; + QByteArray m_soundCompression; + PDFObject m_soundCompressionParameters; + PDFObject m_streamObject; +}; + +} // namespace pdf + +#endif // PDFMULTIMEDIA_H