diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index 79ad3c8..0bede62 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -268,6 +268,258 @@ PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject obj lineAnnotation->m_captionOffset == QPointF(captionOffset[0], captionOffset[1]); } } + else if (subtype == "Square" || subtype == "Circle") + { + PDFSimpleGeometryAnnotation* annotation = new PDFSimpleGeometryAnnotation((subtype == "Square") ? AnnotationType::Square : AnnotationType::Circle); + result.reset(annotation); + + annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); + annotation->m_effect = PDFAnnotationBorderEffect::parse(document, dictionary->get("BE")); + + std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); + if (differenceRectangle.size() == 4) + { + annotation->m_geometryRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); + if (!annotation->m_geometryRectangle.isValid()) + { + annotation->m_geometryRectangle = QRectF(); + } + } + } + else if (subtype == "Polygon" || subtype == "PolyLine") + { + PDFPolygonalGeometryAnnotation* annotation = new PDFPolygonalGeometryAnnotation((subtype == "Polygon") ? AnnotationType::Polygon : AnnotationType::Polyline); + result.reset(annotation); + + std::vector vertices = loader.readNumberArrayFromDictionary(dictionary, "Vertices"); + const size_t count = vertices.size() / 2; + annotation->m_vertices.reserve(count); + for (size_t i = 0; i < count; ++i) + { + annotation->m_vertices.emplace_back(vertices[2 * i], vertices[2 * i + 1]); + } + + std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); + if (lineEndings.size() == 2) + { + annotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); + annotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); + } + + annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); + annotation->m_effect = PDFAnnotationBorderEffect::parse(document, dictionary->get("BE")); + + constexpr const std::array, 3> intents = { + std::pair{ "PolygonCloud", PDFPolygonalGeometryAnnotation::Intent::Cloud }, + std::pair{ "PolyLineDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension }, + std::pair{ "PolygonDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension } + }; + + annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFPolygonalGeometryAnnotation::Intent::None); + annotation->m_measure = document->getObject(dictionary->get("Measure")); + } + else if (subtype == "Highlight" || + subtype == "Underline" || + subtype == "Squiggly" || + subtype == "StrikeOut") + { + AnnotationType type = AnnotationType::Highlight; + if (subtype == "Underline") + { + type = AnnotationType::Underline; + } + else if (subtype == "Squiggly") + { + type = AnnotationType::Squiggly; + } + else if (subtype == "StrikeOut") + { + type = AnnotationType::StrikeOut; + } + + PDFHighlightAnnotation* annotation = new PDFHighlightAnnotation(type); + result.reset(annotation); + + annotation->m_highlightArea = parseQuadrilaterals(document, dictionary->get("QuadPoints"), annotationsRectangle); + } + else if (subtype == "Caret") + { + PDFCaretAnnotation* annotation = new PDFCaretAnnotation(); + result.reset(annotation); + + std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); + if (differenceRectangle.size() == 4) + { + annotation->m_caretRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); + if (!annotation->m_caretRectangle.isValid()) + { + annotation->m_caretRectangle = QRectF(); + } + } + + annotation->m_symbol = (loader.readNameFromDictionary(dictionary, "Sy") == "P") ? PDFCaretAnnotation::Symbol::Paragraph : PDFCaretAnnotation::Symbol::None; + } + else if (subtype == "Stamp") + { + PDFStampAnnotation* annotation = new PDFStampAnnotation(); + result.reset(annotation); + + constexpr const std::array, 14> stamps = { + std::pair{ "Approved", PDFStampAnnotation::Stamp::Approved }, + std::pair{ "AsIs", PDFStampAnnotation::Stamp::AsIs }, + std::pair{ "Confidential", PDFStampAnnotation::Stamp::Confidential }, + std::pair{ "Departmental", PDFStampAnnotation::Stamp::Departmental }, + std::pair{ "Draft", PDFStampAnnotation::Stamp::Draft }, + std::pair{ "Experimental", PDFStampAnnotation::Stamp::Experimental }, + std::pair{ "Expired", PDFStampAnnotation::Stamp::Expired }, + std::pair{ "Final", PDFStampAnnotation::Stamp::Final }, + std::pair{ "ForComment", PDFStampAnnotation::Stamp::ForComment }, + std::pair{ "ForPublicRelease", PDFStampAnnotation::Stamp::ForPublicRelease }, + std::pair{ "NotApproved", PDFStampAnnotation::Stamp::NotApproved }, + std::pair{ "NotForPublicRelease", PDFStampAnnotation::Stamp::NotForPublicRelease }, + std::pair{ "Sold", PDFStampAnnotation::Stamp::Sold }, + std::pair{ "TopSecret", PDFStampAnnotation::Stamp::TopSecret } + }; + + annotation->m_stamp = loader.readEnumByName(dictionary->get("Name"), stamps.begin(), stamps.end(), PDFStampAnnotation::Stamp::Draft); + } + else if (subtype == "Ink") + { + PDFInkAnnotation* annotation = new PDFInkAnnotation(); + result.reset(annotation); + + PDFObject inkList = document->getObject(dictionary->get("InkList")); + if (inkList.isArray()) + { + const PDFArray* inkListArray = inkList.getArray(); + for (size_t i = 0, count = inkListArray->getCount(); i < count; ++i) + { + std::vector points = loader.readNumberArray(inkListArray->getItem(i)); + const size_t pointCount = points.size() / 2; + + for (size_t j = 0; j < pointCount; ++j) + { + QPointF point(points[j * 2], points[j * 2 + 1]); + + if (j == 0) + { + annotation->m_inkPath.moveTo(point); + } + else + { + annotation->m_inkPath.lineTo(point); + } + } + annotation->m_inkPath.closeSubpath(); + } + } + } + else if (subtype == "Popup") + { + PDFPopupAnnotation* annotation = new PDFPopupAnnotation(); + result.reset(annotation); + + annotation->m_opened = loader.readBooleanFromDictionary(dictionary, "Open", false); + } + else if (subtype == "FileAttachment") + { + PDFFileAttachmentAnnotation* annotation = new PDFFileAttachmentAnnotation(); + result.reset(annotation); + + annotation->m_fileSpecification = PDFFileSpecification::parse(document, dictionary->get("FS")); + + constexpr const std::array, 4> icons = { + std::pair{ "Graph", PDFFileAttachmentAnnotation::Icon::Graph }, + std::pair{ "Paperclip", PDFFileAttachmentAnnotation::Icon::Paperclip }, + std::pair{ "PushPin", PDFFileAttachmentAnnotation::Icon::PushPin }, + std::pair{ "Tag", PDFFileAttachmentAnnotation::Icon::Tag } + }; + + annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), PDFFileAttachmentAnnotation::Icon::PushPin); + } + else if (subtype == "Sound") + { + PDFSoundAnnotation* annotation = new PDFSoundAnnotation(); + result.reset(annotation); + + annotation->m_sound = PDFSound::parse(document, dictionary->get("Sound")); + + constexpr const std::array, 2> icons = { + std::pair{ "Speaker", PDFSoundAnnotation::Icon::Speaker }, + std::pair{ "Mic", PDFSoundAnnotation::Icon::Microphone } + }; + + annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), PDFSoundAnnotation::Icon::Speaker); + } + else if (subtype == "Movie") + { + PDFMovieAnnotation* annotation = new PDFMovieAnnotation(); + result.reset(annotation); + + annotation->m_movieTitle = loader.readStringFromDictionary(dictionary, "T"); + annotation->m_movie = PDFMovie::parse(document, dictionary->get("Movie")); + + PDFObject activation = document->getObject(dictionary->get("A")); + if (activation.isBool()) + { + annotation->m_playMovie = activation.getBool(); + } + else if (activation.isDictionary()) + { + annotation->m_playMovie = true; + annotation->m_movieActivation = PDFMovieActivation::parse(document, activation); + } + } + else if (subtype == "Screen") + { + PDFScreenAnnotation* annotation = new PDFScreenAnnotation(); + result.reset(annotation); + + annotation->m_screenTitle = loader.readTextStringFromDictionary(dictionary, "T", QString()); + annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(document, dictionary->get("MK")); + annotation->m_action = PDFAction::parse(document, dictionary->get("A")); + annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(document, dictionary->get("AA")); + } + else if (subtype == "Widget") + { + PDFWidgetAnnotation* annotation = new PDFWidgetAnnotation(); + result.reset(annotation); + + constexpr const std::array, 5> highlightModes = { + std::pair{ "N", PDFWidgetAnnotation::HighlightMode::None }, + std::pair{ "I", PDFWidgetAnnotation::HighlightMode::Invert }, + std::pair{ "O", PDFWidgetAnnotation::HighlightMode::Outline }, + std::pair{ "P", PDFWidgetAnnotation::HighlightMode::Push }, + std::pair{ "T", PDFWidgetAnnotation::HighlightMode::Toggle } + }; + + annotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightModes.begin(), highlightModes.end(), PDFWidgetAnnotation::HighlightMode::Invert); + annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(document, dictionary->get("MK")); + annotation->m_action = PDFAction::parse(document, dictionary->get("A")); + annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(document, dictionary->get("AA")); + } + else if (subtype == "PrinterMark") + { + PDFPrinterMarkAnnotation* annotation = new PDFPrinterMarkAnnotation(); + result.reset(annotation); + } + else if (subtype == "TrapNet") + { + PDFTrapNetworkAnnotation* annotation = new PDFTrapNetworkAnnotation(); + result.reset(annotation); + } + else if (subtype == "Watermark") + { + PDFWatermarkAnnotation* annotation = new PDFWatermarkAnnotation(); + result.reset(annotation); + + if (const PDFDictionary* fixedPrintDictionary = document->getDictionaryFromObject(dictionary->get("FixedPrint"))) + { + annotation->m_matrix = loader.readMatrixFromDictionary(fixedPrintDictionary, "Matrix", QMatrix()); + annotation->m_relativeHorizontalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "H", 0.0); + annotation->m_relativeVerticalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "V", 0.0); + } + } if (!result) { @@ -401,4 +653,83 @@ PDFAnnotationCalloutLine PDFAnnotationCalloutLine::parse(const PDFDocument* docu return PDFAnnotationCalloutLine(); } +PDFAnnotationAppearanceCharacteristics PDFAnnotationAppearanceCharacteristics::parse(const PDFDocument* document, PDFObject object) +{ + PDFAnnotationAppearanceCharacteristics result; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + + result.m_rotation = loader.readIntegerFromDictionary(dictionary, "R", 0); + result.m_borderColor = loader.readNumberArrayFromDictionary(dictionary, "BC"); + result.m_backgroundColor = loader.readNumberArrayFromDictionary(dictionary, "BG"); + result.m_normalCaption = loader.readTextStringFromDictionary(dictionary, "CA", QString()); + result.m_rolloverCaption = loader.readTextStringFromDictionary(dictionary, "RC", QString()); + result.m_downCaption = loader.readTextStringFromDictionary(dictionary, "AC", QString()); + result.m_normalIcon = document->getObject(dictionary->get("I")); + result.m_rolloverIcon = document->getObject(dictionary->get("RI")); + result.m_downIcon = document->getObject(dictionary->get("IX")); + result.m_iconFit = PDFAnnotationIconFitInfo::parse(document, dictionary->get("IF")); + result.m_pushButtonMode = static_cast(loader.readIntegerFromDictionary(dictionary, "TP", PDFInteger(PDFAnnotationAppearanceCharacteristics::PushButtonMode::NoIcon))); + } + return result; +} + +PDFAnnotationIconFitInfo PDFAnnotationIconFitInfo::parse(const PDFDocument* document, PDFObject object) +{ + PDFAnnotationIconFitInfo info; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + + constexpr const std::array, 4> scaleConditions = { + std::pair{ "A", PDFAnnotationIconFitInfo::ScaleCondition::Always }, + std::pair{ "B", PDFAnnotationIconFitInfo::ScaleCondition::ScaleBigger }, + std::pair{ "S", PDFAnnotationIconFitInfo::ScaleCondition::ScaleSmaller }, + std::pair{ "N", PDFAnnotationIconFitInfo::ScaleCondition::Never } + }; + + constexpr const std::array, 2> scaleTypes = { + std::pair{ "A", PDFAnnotationIconFitInfo::ScaleType::Anamorphic }, + std::pair{ "P", PDFAnnotationIconFitInfo::ScaleType::Proportional } + }; + + std::vector point = loader.readNumberArrayFromDictionary(dictionary, "A"); + if (point.size() != 2) + { + point.resize(2, 0.5); + } + + info.m_scaleCondition = loader.readEnumByName(dictionary->get("SW"), scaleConditions.begin(), scaleConditions.end(), PDFAnnotationIconFitInfo::ScaleCondition::Always); + info.m_scaleType = loader.readEnumByName(dictionary->get("S"), scaleTypes.begin(), scaleTypes.end(), PDFAnnotationIconFitInfo::ScaleType::Proportional); + info.m_relativeProportionalPosition = QPointF(point[0], point[1]); + info.m_fullBox = loader.readBooleanFromDictionary(dictionary, "FB", false); + } + + return info; +} + +PDFAnnotationAdditionalActions PDFAnnotationAdditionalActions::parse(const PDFDocument* document, PDFObject object) +{ + PDFAnnotationAdditionalActions result; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + result.m_actions[CursorEnter] = PDFAction::parse(document, dictionary->get("E")); + result.m_actions[CursorLeave] = PDFAction::parse(document, dictionary->get("X")); + result.m_actions[MousePressed] = PDFAction::parse(document, dictionary->get("D")); + result.m_actions[MouseReleased] = PDFAction::parse(document, dictionary->get("U")); + result.m_actions[FocusIn] = PDFAction::parse(document, dictionary->get("Fo")); + result.m_actions[FocusOut] = PDFAction::parse(document, dictionary->get("Bl")); + result.m_actions[PageOpened] = PDFAction::parse(document, dictionary->get("PO")); + result.m_actions[PageClosed] = PDFAction::parse(document, dictionary->get("PC")); + result.m_actions[PageShow] = PDFAction::parse(document, dictionary->get("PV")); + result.m_actions[PageHide] = PDFAction::parse(document, dictionary->get("PI")); + } + + return result; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index e2d6f5e..205a947 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Jakub Melka +// Copyright (C) 2020 Jakub Melka // // This file is part of PdfForQt. // @@ -21,6 +21,8 @@ #include "pdfglobal.h" #include "pdfobject.h" #include "pdfaction.h" +#include "pdffile.h" +#include "pdfmultimedia.h" #include @@ -51,7 +53,7 @@ enum class AnnotationType Popup, FileAttachment, Sound, - Moview, + Movie, Widget, Screen, PrinterMark, @@ -241,7 +243,7 @@ public: /// Parses annotation callout line from the object. If object is invalid, then /// invalid callout line is constructed. /// \param document Document - /// \param object Appearance streams object + /// \param object Callout line object static PDFAnnotationCalloutLine parse(const PDFDocument* document, PDFObject object); bool isValid() const { return m_type != Type::Invalid; } @@ -254,6 +256,128 @@ private: std::array m_points; }; +/// Information about annotation icon fitting (in the widget) +class PDFAnnotationIconFitInfo +{ +public: + inline explicit PDFAnnotationIconFitInfo() = default; + + enum class ScaleCondition + { + Always, + ScaleBigger, + ScaleSmaller, + Never + }; + + enum class ScaleType + { + Anamorphic, ///< Do not keep aspect ratio, fit whole annotation rectangle + Proportional ///< Keep aspect ratio, annotation rectangle may not be filled fully with icon + }; + + /// Parses annotation appearance icon fit info from the object. If object is invalid, then + /// default appearance icon fit info is constructed. + /// \param document Document + /// \param object Appearance icon fit info object + static PDFAnnotationIconFitInfo parse(const PDFDocument* document, PDFObject object); + +private: + ScaleCondition m_scaleCondition = ScaleCondition::Always; + ScaleType m_scaleType = ScaleType::Proportional; + QPointF m_relativeProportionalPosition = QPointF(0.5, 0.5); + bool m_fullBox = false; +}; + +/// Additional appearance characteristics used for constructing of appearance +/// stream to display annotation on the screen (or just paint it). +class PDFAnnotationAppearanceCharacteristics +{ +public: + inline explicit PDFAnnotationAppearanceCharacteristics() = default; + + enum class PushButtonMode + { + NoIcon, + NoCaption, + IconWithCaptionBelow, + IconWithCaptionAbove, + IconWithCaptionRight, + IconWithCaptionLeft, + IconWithCaptionOverlaid + }; + + /// Number of degrees by which the widget annotation is rotated + /// counterclockwise relative to the page. + PDFInteger getRotation() const { return m_rotation; } + const std::vector& getBorderColor() const { return m_borderColor; } + const std::vector& getBackgroundColor() const { return m_backgroundColor; } + const QString& getNormalCaption() const { return m_normalCaption; } + const QString& getRolloverCaption() const { return m_rolloverCaption; } + const QString& getDownCaption() const { return m_downCaption; } + const PDFObject& getNormalIcon() const { return m_normalIcon; } + const PDFObject& getRolloverIcon() const { return m_rolloverIcon; } + const PDFObject& getDownIcon() const { return m_downIcon; } + const PDFAnnotationIconFitInfo& getIconFit() const { return m_iconFit; } + PushButtonMode getPushButtonMode() const { return m_pushButtonMode; } + + /// Parses annotation appearance characteristics from the object. If object is invalid, then + /// default appearance characteristics is constructed. + /// \param document Document + /// \param object Appearance characteristics object + static PDFAnnotationAppearanceCharacteristics parse(const PDFDocument* document, PDFObject object); + +private: + PDFInteger m_rotation = 0; + std::vector m_borderColor; + std::vector m_backgroundColor; + QString m_normalCaption; + QString m_rolloverCaption; + QString m_downCaption; + PDFObject m_normalIcon; + PDFObject m_rolloverIcon; + PDFObject m_downIcon; + PDFAnnotationIconFitInfo m_iconFit; + PushButtonMode m_pushButtonMode = PushButtonMode::NoIcon; +}; + +/// Storage for annotation additional actions +class PDFAnnotationAdditionalActions +{ +public: + + enum Action + { + CursorEnter, + CursorLeave, + MousePressed, + MouseReleased, + FocusIn, + FocusOut, + PageOpened, + PageClosed, + PageShow, + PageHide, + End + }; + + inline explicit PDFAnnotationAdditionalActions() = default; + + /// Returns action for given type. If action is invalid, + /// or not present, nullptr is returned. + /// \param action Action type + const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } + + /// Parses annotation additional actions from the object. If object is invalid, then + /// empty additional actions is constructed. + /// \param document Document + /// \param object Additional actions object + static PDFAnnotationAdditionalActions parse(const PDFDocument* document, PDFObject object); + +private: + std::array m_actions; +}; + class PDFAnnotation; class PDFMarkupAnnotation; class PDFTextAnnotation; @@ -538,6 +662,365 @@ private: QPointF m_captionOffset; }; +/// Simple geometry annotation. +/// Square and circle annotations displays rectangle or ellipse on the page. +/// Name is a bit strange (because rectangle may not be a square or circle is not ellipse), +/// but it is defined in PDF specification, so we will use these terms. +class PDFSimpleGeometryAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFSimpleGeometryAnnotation(AnnotationType type) : + m_type(type) + { + + } + + virtual AnnotationType getType() const override { return m_type; } + + const std::vector& getInteriorColor() const { return m_interiorColor; } + const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } + const QRectF& getGeometryRectangle() const { return m_geometryRectangle; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + AnnotationType m_type; + std::vector m_interiorColor; + PDFAnnotationBorderEffect m_effect; + QRectF m_geometryRectangle; +}; + +/// Polygonal geometry, consists of polygon or polyline geometry. Polygon annotation +/// displays closed polygon (potentially filled), polyline annotation displays +/// polyline, which is not closed. +class PDFPolygonalGeometryAnnotation : public PDFMarkupAnnotation +{ +public: + enum class Intent + { + None, + Cloud, + Dimension + }; + + inline explicit PDFPolygonalGeometryAnnotation(AnnotationType type) : + m_type(type), + m_intent(Intent::None) + { + + } + + virtual AnnotationType getType() const override { return m_type; } + + const std::vector& getVertices() const { return m_vertices; } + AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } + AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } + const std::vector& getInteriorColor() const { return m_interiorColor; } + const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } + Intent getIntent() const { return m_intent; } + const PDFObject& getMeasure() const { return m_measure; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + AnnotationType m_type; + std::vector m_vertices; + AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; + AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; + std::vector m_interiorColor; + PDFAnnotationBorderEffect m_effect; + Intent m_intent; + PDFObject m_measure; +}; + +/// Annotation for text highlighting. Can highlight, underline, strikeout, +/// or squiggly underline the text. +class PDFHighlightAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFHighlightAnnotation(AnnotationType type) : + m_type(type) + { + + } + + virtual AnnotationType getType() const override { return m_type; } + + const PDFAnnotationQuadrilaterals& getHiglightArea() const { return m_highlightArea; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + AnnotationType m_type; + PDFAnnotationQuadrilaterals m_highlightArea; +}; + +/// Annotation for visual symbol that indicates presence of text edits. +class PDFCaretAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFCaretAnnotation() = default; + + enum class Symbol + { + None, + Paragraph + }; + + virtual AnnotationType getType() const override { return AnnotationType::Caret; } + + const QRectF& getCaretRectangle() const { return m_caretRectangle; } + Symbol getSymbol() const { return m_symbol; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + QRectF m_caretRectangle; + Symbol m_symbol = Symbol::None; +}; + +/// Annotation for stamps. Displays text or graphics intended to look +/// as if they were stamped on the paper. +class PDFStampAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFStampAnnotation() = default; + + enum class Stamp + { + Approved, + AsIs, + Confidential, + Departmental, + Draft, + Experimental, + Expired, + Final, + ForComment, + ForPublicRelease, + NotApproved, + NotForPublicRelease, + Sold, + TopSecret + }; + + virtual AnnotationType getType() const override { return AnnotationType::Stamp; } + + Stamp getStamp() const { return m_stamp; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + Stamp m_stamp = Stamp::Draft; +}; + +/// Ink annotation. Represents a path composed of disjoint polygons. +class PDFInkAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFInkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Ink; } + + const QPainterPath& getInkPath() const { return m_inkPath; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + QPainterPath m_inkPath; +}; + +/// Popup annotation. Displays text in popup window for markup annotations. +/// This annotation contains field to associated annotation, for which +/// is window displayed, and window state (open/closed). +class PDFPopupAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFPopupAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Popup; } + + bool isOpened() const { return m_opened; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + bool m_opened = false; +}; + +/// File attachment annotation contains reference to (embedded or external) file. +/// So it is a link to specified file. Activating annotation enables user to view +/// or store attached file in the filesystem. +class PDFFileAttachmentAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFFileAttachmentAnnotation() = default; + + enum class Icon + { + Graph, + Paperclip, + PushPin, + Tag + }; + + virtual AnnotationType getType() const override { return AnnotationType::FileAttachment; } + + const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } + Icon getIcon() const { return m_icon; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + PDFFileSpecification m_fileSpecification; + Icon m_icon = Icon::PushPin; +}; + +/// Sound annotation contains sound, which is played, when +/// annotation is activated. +class PDFSoundAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFSoundAnnotation() = default; + + enum class Icon + { + Speaker, + Microphone + }; + + virtual AnnotationType getType() const override { return AnnotationType::Sound; } + + const PDFSound& getSound() const { return m_sound; } + Icon getIcon() const { return m_icon; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + PDFSound m_sound; + Icon m_icon = Icon::Speaker; +}; + +/// Movie annotation contains movie or sound, which is played, when +/// annotation is activated. +class PDFMovieAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFMovieAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Movie; } + + const QString& getMovieTitle() const { return m_movieTitle; } + bool isMovieToBePlayed() const { return m_playMovie; } + const PDFMovie& getMovie() const { return m_movie; } + const PDFMovieActivation& getMovieActivation() const { return m_movieActivation; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + QString m_movieTitle; + bool m_playMovie = true; + PDFMovie m_movie; + PDFMovieActivation m_movieActivation; +}; + +/// Screen action represents area of page in which is media played. +/// See also Rendition actions and their relationship to this annotation. +class PDFScreenAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFScreenAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Screen; } + + const QString& getScreenTitle() const { return m_screenTitle; } + const PDFAnnotationAppearanceCharacteristics& getAppearanceCharacteristics() const { return m_appearanceCharacteristics; } + const PDFAction* getAction() const { return m_action.get(); } + const PDFAnnotationAdditionalActions& getAdditionalActions() const { return m_additionalActions; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + QString m_screenTitle; + PDFAnnotationAppearanceCharacteristics m_appearanceCharacteristics; + PDFActionPtr m_action; + PDFAnnotationAdditionalActions m_additionalActions; +}; + +/// Widget annotation represents form fileds for interactive forms. +/// Annotation's dictionary is merged with form field dictionary, +/// it can be done, because dictionaries doesn't overlap. +class PDFWidgetAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFWidgetAnnotation() = default; + + enum class HighlightMode + { + None, + Invert, + Outline, + Push, + Toggle + }; + + virtual AnnotationType getType() const override { return AnnotationType::Widget; } + + HighlightMode getHighlightMode() const { return m_highlightMode; } + const PDFAnnotationAppearanceCharacteristics& getAppearanceCharacteristics() const { return m_appearanceCharacteristics; } + const PDFAction* getAction() const { return m_action.get(); } + const PDFAnnotationAdditionalActions& getAdditionalActions() const { return m_additionalActions; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + HighlightMode m_highlightMode = HighlightMode::Invert; + PDFAnnotationAppearanceCharacteristics m_appearanceCharacteristics; + PDFActionPtr m_action; + PDFAnnotationAdditionalActions m_additionalActions; +}; + +/// Printer mark annotation represents graphics symbol, mark, or other +/// graphic feature to assist printing production. +class PDFPrinterMarkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFPrinterMarkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::PrinterMark; } +}; + +/// Trapping characteristics for the page +class PDFTrapNetworkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFTrapNetworkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::TrapNet; } +}; + +/// Watermark annotation represents watermark displayed on the page, +/// for example, if it is printed. Watermarks are displayed at fixed +/// position and size on the page. +class PDFWatermarkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFWatermarkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Watermark; } + + const QMatrix& getMatrix() const { return m_matrix; } + PDFReal getRelativeHorizontalOffset() const { return m_relativeHorizontalOffset; } + PDFReal getRelativeVerticalOffset() const { return m_relativeVerticalOffset; } + +private: + friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFDocument* document, PDFObject object); + + QMatrix m_matrix; + PDFReal m_relativeHorizontalOffset = 0.0; + PDFReal m_relativeVerticalOffset = 0.0; +}; + } // namespace pdf #endif // PDFANNOTATION_H diff --git a/PdfForQtLib/sources/pdfmultimedia.cpp b/PdfForQtLib/sources/pdfmultimedia.cpp index 0276933..914b3bd 100644 --- a/PdfForQtLib/sources/pdfmultimedia.cpp +++ b/PdfForQtLib/sources/pdfmultimedia.cpp @@ -18,6 +18,8 @@ #include "pdfmultimedia.h" #include "pdfdocument.h" +#include + namespace pdf { @@ -537,4 +539,127 @@ PDFMediaScreenParameters PDFMediaScreenParameters::parse(const PDFDocument* docu return PDFMediaScreenParameters(); } +PDFMovie PDFMovie::parse(const PDFDocument* document, PDFObject object) +{ + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFMovie result; + + PDFDocumentDataLoaderDecorator loader(document); + result.m_movieFile = PDFFileSpecification::parse(document, dictionary->get("F")); + std::vector windowSizeArray = loader.readIntegerArrayFromDictionary(dictionary, "Aspect"); + if (windowSizeArray.size() == 2) + { + result.m_windowSize = QSize(windowSizeArray[0], windowSizeArray[1]); + } + result.m_rotationAngle = loader.readIntegerFromDictionary(dictionary, "Rotate", 0); + + PDFObject posterObject = document->getObject(dictionary->get("Poster")); + if (posterObject.isBool()) + { + result.m_showPoster = posterObject.getBool(); + } + else if (posterObject.isStream()) + { + result.m_showPoster = true; + result.m_poster = posterObject; + } + + return result; + } + + return PDFMovie(); +} + +PDFMovieActivation PDFMovieActivation::parse(const PDFDocument* document, PDFObject object) +{ + PDFMovieActivation result; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + + constexpr const std::array, 4> modes = { + std::pair{ "Once", Mode::Once }, + std::pair{ "Open", Mode::Open }, + std::pair{ "Repeat", Mode::Repeat }, + std::pair{ "Palindrome", Mode::Palindrome } + }; + + std::vector scale = loader.readIntegerArrayFromDictionary(dictionary, "FWScale"); + if (scale.size() != 2) + { + scale.resize(2, 0); + } + std::vector relativePosition = loader.readNumberArrayFromDictionary(dictionary, "FWPosition"); + if (relativePosition.size() != 2) + { + relativePosition.resize(2, 0.5); + } + + result.m_start = parseMovieTime(document, dictionary->get("Start")); + result.m_duration = parseMovieTime(document, dictionary->get("Duration")); + result.m_rate = loader.readNumberFromDictionary(dictionary, "Rate", 1.0); + result.m_volume = loader.readNumberFromDictionary(dictionary, "Volume", 1.0); + result.m_showControls = loader.readBooleanFromDictionary(dictionary, "ShowControls", false); + result.m_synchronous = loader.readBooleanFromDictionary(dictionary, "Synchronous", false); + result.m_mode = loader.readEnumByName(dictionary->get("Mode"), modes.cbegin(), modes.cend(), Mode::Once); + result.m_scaleNumerator = scale[0]; + result.m_scaleDenominator = scale[1]; + result.m_relativeWindowPosition = QPointF(relativePosition[0], relativePosition[1]); + } + + return result; +} + +PDFMovieActivation::MovieTime PDFMovieActivation::parseMovieTime(const PDFDocument* document, PDFObject object) +{ + MovieTime result; + + object = document->getObject(object); + if (object.isInt()) + { + result.value = object.getInteger(); + } + else if (object.isString()) + { + result.value = parseMovieTimeFromString(object.getString()); + } + else if (object.isArray()) + { + const PDFArray* objectArray = object.getArray(); + if (objectArray->getCount() == 2) + { + PDFDocumentDataLoaderDecorator loader(document); + result.unitsPerSecond = loader.readInteger(objectArray->getItem(1), 0); + + object = document->getObject(objectArray->getItem(0)); + if (object.isInt()) + { + result.value = object.getInteger(); + } + else if (object.isString()) + { + result.value = parseMovieTimeFromString(object.getString()); + } + } + } + + return result; +} + +PDFInteger PDFMovieActivation::parseMovieTimeFromString(const QByteArray& string) +{ + // According to the specification, the string contains 64-bit signed integer, + // in big-endian format. + if (string.size() == sizeof(quint64)) + { + quint64 result = reinterpret_cast(string.data()); + qFromBigEndian(&result, qsizetype(sizeof(decltype(result))), &result); + return static_cast(result); + } + + return 0; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfmultimedia.h b/PdfForQtLib/sources/pdfmultimedia.h index c0dc93c..2a7d397 100644 --- a/PdfForQtLib/sources/pdfmultimedia.h +++ b/PdfForQtLib/sources/pdfmultimedia.h @@ -532,6 +532,82 @@ private: PDFObject m_streamObject; }; +/// Movie object, see chapter 9.3 in PDF 1.7 reference +class PDFMovie +{ +public: + explicit inline PDFMovie() = default; + + const PDFFileSpecification* getMovieFileSpecification() const { return &m_movieFile; } + QSize getWindowSize() const { return m_windowSize; } + PDFInteger getRotationAngle() const { return m_rotationAngle; } + bool isPosterVisible() const { return m_showPoster; } + const PDFObject& getPosterObject() const { return m_poster; } + + /// Creates a new movie from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDFMovie parse(const PDFDocument* document, PDFObject object); + +private: + PDFFileSpecification m_movieFile; + QSize m_windowSize; + PDFInteger m_rotationAngle = 0; + bool m_showPoster = false; + PDFObject m_poster; +}; + +/// Movie activation object, see table 9.31 in PDF 1.7 reference +class PDFMovieActivation +{ +public: + explicit inline PDFMovieActivation() = default; + + struct MovieTime + { + PDFInteger value = 0; + std::optional unitsPerSecond; + }; + + enum class Mode + { + Once, + Open, + Repeat, + Palindrome + }; + + MovieTime getStartTime() const { return m_start; } + MovieTime getDuration() const { return m_duration; } + PDFReal getRate() const { return m_rate; } + PDFReal getVolume() const { return m_volume; } + bool isShowControls() const { return m_showControls; } + bool isSynchronous() const { return m_synchronous; } + Mode getMode() const { return m_mode; } + bool hasScale() const { return m_scaleDenominator != 0; } + PDFInteger getScaleNumerator() const { return m_scaleNumerator; } + PDFInteger getScaleDenominator() const { return m_scaleDenominator; } + QPointF getRelativeWindowPosition() const { return m_relativeWindowPosition; } + + /// Creates a new moview from the object. If data are invalid, then invalid object + /// is returned, no exception is thrown. + static PDFMovieActivation parse(const PDFDocument* document, PDFObject object); + +private: + static MovieTime parseMovieTime(const PDFDocument* document, PDFObject object); + static PDFInteger parseMovieTimeFromString(const QByteArray& string); + + MovieTime m_start; + MovieTime m_duration; + PDFReal m_rate = 1.0; + PDFReal m_volume = 1.0; + bool m_showControls = false; + bool m_synchronous = false; + Mode m_mode = Mode::Once; + PDFInteger m_scaleNumerator = 0; + PDFInteger m_scaleDenominator = 0; + QPointF m_relativeWindowPosition; +}; + } // namespace pdf #endif // PDFMULTIMEDIA_H