From 038548c39167eff5690d4719fcad079c9f789f76 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 14 Dec 2018 19:41:12 +0100 Subject: [PATCH] Catalog (first part) --- PdfForQt.pro.user | 8 +- PdfForQtLib/PdfForQtLib.pro | 6 +- PdfForQtLib/sources/pdfcatalog.cpp | 318 +++++++++++++++++++++++++++++ PdfForQtLib/sources/pdfcatalog.h | 162 +++++++++++++++ PdfForQtLib/sources/pdfdocument.h | 12 +- PdfForQtLib/sources/pdfglobal.h | 6 + 6 files changed, 500 insertions(+), 12 deletions(-) create mode 100644 PdfForQtLib/sources/pdfcatalog.cpp create mode 100644 PdfForQtLib/sources/pdfcatalog.h diff --git a/PdfForQt.pro.user b/PdfForQt.pro.user index eb43252..4ad65d7 100644 --- a/PdfForQt.pro.user +++ b/PdfForQt.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -67,7 +67,7 @@ Desktop Qt 5.11.2 MSVC2017 64bit Desktop Qt 5.11.2 MSVC2017 64bit qt.qt5.5112.win64_msvc2017_64_kit - 1 + 0 0 1 @@ -296,7 +296,7 @@ UnitTests/UnitTests.pro - K:/Programming/PDF/PDF_For_Qt/bin_release/UnitTests/.. + K:/Programming/PDF/PDF_For_Qt/bin_debug/UnitTests/.. 3768 false true @@ -353,7 +353,7 @@ PdfForQtViewer/PdfForQtViewer.pro - K:/Programming/PDF/PDF_For_Qt/bin_release/PdfForQtViewer/.. + K:/Programming/PDF/PDF_For_Qt/bin_debug/PdfForQtViewer/.. 3768 false true diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 13a6a37..e3ab1af 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -42,7 +42,8 @@ SOURCES += \ sources/pdfdocumentreader.cpp \ sources/pdfxreftable.cpp \ sources/pdfvisitor.cpp \ - sources/pdfencoding.cpp + sources/pdfencoding.cpp \ + sources/pdfcatalog.cpp HEADERS += \ sources/pdfobject.h \ @@ -54,7 +55,8 @@ HEADERS += \ sources/pdfxreftable.h \ sources/pdfflatmap.h \ sources/pdfvisitor.h \ - sources/pdfencoding.h + sources/pdfencoding.h \ + sources/pdfcatalog.h unix { target.path = /usr/lib diff --git a/PdfForQtLib/sources/pdfcatalog.cpp b/PdfForQtLib/sources/pdfcatalog.cpp new file mode 100644 index 0000000..4114c63 --- /dev/null +++ b/PdfForQtLib/sources/pdfcatalog.cpp @@ -0,0 +1,318 @@ +// Copyright (C) 2018 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 "pdfcatalog.h" +#include "pdfparser.h" +#include "pdfdocument.h" + +namespace pdf +{ + +static constexpr const char* PDF_VIEWER_PREFERENCES_DICTIONARY = "ViewerPreferences"; +static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR = "HideToolbar"; +static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_MENUBAR = "HideMenubar"; +static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_WINDOW_UI = "HideWindowUI"; +static constexpr const char* PDF_VIEWER_PREFERENCES_FIT_WINDOW = "FitWindow"; +static constexpr const char* PDF_VIEWER_PREFERENCES_CENTER_WINDOW = "CenterWindow"; +static constexpr const char* PDF_VIEWER_PREFERENCES_DISPLAY_DOCUMENT_TITLE = "DisplayDocTitle"; +static constexpr const char* PDF_VIEWER_PREFERENCES_NON_FULLSCREEN_PAGE_MODE = "NonFullScreenPageMode"; +static constexpr const char* PDF_VIEWER_PREFERENCES_DIRECTION = "Direction"; +static constexpr const char* PDF_VIEWER_PREFERENCES_VIEW_AREA = "ViewArea"; +static constexpr const char* PDF_VIEWER_PREFERENCES_VIEW_CLIP = "ViewClip"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_AREA = "PrintArea"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_CLIP = "PrintClip"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_SCALING = "PrintScaling"; +static constexpr const char* PDF_VIEWER_PREFERENCES_DUPLEX = "Duplex"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE = "PickTrayByPDFSize"; +static constexpr const char* PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES = "NumCopies"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE = "PrintPageRange"; + +PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* document) +{ + if (!catalog.isDictionary()) + { + throw PDFParserException(PDFTranslationContext::tr("Catalog must be a dictionary.")); + } + + PDFCatalog catalogObject; + catalogObject.m_viewerPreferences = PDFViewerPreferences::parse(catalog, document); + return catalogObject; +} + +PDFViewerPreferences PDFViewerPreferences::parse(const PDFObject& catalogDictionary, const PDFDocument* document) +{ + PDFViewerPreferences result; + + if (!catalogDictionary.isDictionary()) + { + throw PDFParserException(PDFTranslationContext::tr("Catalog must be a dictionary.")); + } + + const PDFDictionary* dictionary = catalogDictionary.getDictionary(); + if (dictionary->hasKey(PDF_VIEWER_PREFERENCES_DICTIONARY)) + { + const PDFObject& viewerPreferencesObject = document->getObject(dictionary->get(PDF_VIEWER_PREFERENCES_DICTIONARY)); + if (viewerPreferencesObject.isDictionary()) + { + // Load the viewer preferences object + const PDFDictionary* viewerPreferencesDictionary = viewerPreferencesObject.getDictionary(); + + auto addFlag = [&result, viewerPreferencesDictionary, document] (const char* name, OptionFlag flag) + { + const PDFObject& flagObject = document->getObject(viewerPreferencesDictionary->get(name)); + if (!flagObject.isNull()) + { + if (flagObject.isBool()) + { + result.m_optionFlags.setFlag(flag, flagObject.getBool()); + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Expected boolean value.")); + } + } + }; + addFlag(PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR, HideToolbar); + addFlag(PDF_VIEWER_PREFERENCES_HIDE_MENUBAR, HideMenubar); + addFlag(PDF_VIEWER_PREFERENCES_HIDE_WINDOW_UI, HideWindowUI); + addFlag(PDF_VIEWER_PREFERENCES_FIT_WINDOW, FitWindow); + addFlag(PDF_VIEWER_PREFERENCES_CENTER_WINDOW, CenterWindow); + addFlag(PDF_VIEWER_PREFERENCES_DISPLAY_DOCUMENT_TITLE, DisplayDocTitle); + addFlag(PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE, PickTrayByPDFSize); + + // Non-fullscreen page mode + const PDFObject& nonFullscreenPageMode = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_NON_FULLSCREEN_PAGE_MODE)); + if (!nonFullscreenPageMode.isNull()) + { + if (!nonFullscreenPageMode.isName()) + { + throw PDFParserException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = nonFullscreenPageMode.getString(); + if (enumName == "UseNone") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseNone; + } + else if (enumName == "UseOutlines") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseOutline; + } + else if (enumName == "UseThumbs") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseThumbnails; + } + else if (enumName == "UseOC") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseOptionalContent; + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + // Direction + const PDFObject& direction = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_DIRECTION)); + if (!direction.isNull()) + { + if (!direction.isName()) + { + throw PDFParserException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = direction.getString(); + if (enumName == "L2R") + { + result.m_direction = Direction::LeftToRight; + } + else if (enumName == "R2L") + { + result.m_direction = Direction::RightToLeft; + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + auto addProperty = [&result, viewerPreferencesDictionary, document] (const char* name, Properties property) + { + const PDFObject& propertyObject = document->getObject(viewerPreferencesDictionary->get(name)); + if (!propertyObject.isNull()) + { + if (propertyObject.isName()) + { + result.m_properties[property] = propertyObject.getString(); + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Expected name.")); + } + } + }; + addProperty(PDF_VIEWER_PREFERENCES_VIEW_AREA, ViewArea); + addProperty(PDF_VIEWER_PREFERENCES_VIEW_CLIP, ViewClip); + addProperty(PDF_VIEWER_PREFERENCES_PRINT_AREA, PrintArea); + addProperty(PDF_VIEWER_PREFERENCES_PRINT_CLIP, PrintClip); + + // Print scaling + const PDFObject& printScaling = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_PRINT_SCALING)); + if (!printScaling.isNull()) + { + if (!printScaling.isName()) + { + throw PDFParserException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = printScaling.getString(); + if (enumName == "None") + { + result.m_printScaling = PrintScaling::None; + } + else if (enumName == "AppDefault") + { + result.m_printScaling = PrintScaling::AppDefault; + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + // Duplex + const PDFObject& duplex = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_DUPLEX)); + if (!duplex.isNull()) + { + if (!duplex.isName()) + { + throw PDFParserException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = duplex.getString(); + if (enumName == "Simplex") + { + result.m_duplex = Duplex::Simplex; + } + else if (enumName == "DuplexFlipShortEdge") + { + result.m_duplex = Duplex::DuplexFlipShortEdge; + } + else if (enumName == "DuplexFlipLongEdge") + { + result.m_duplex = Duplex::DuplexFlipLongEdge; + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + // Print page range + const PDFObject& printPageRange = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE)); + if (!printPageRange.isNull()) + { + if (!duplex.isArray()) + { + throw PDFParserException(PDFTranslationContext::tr("Expected array of integers.")); + } + + // According to PDF Reference 1.7, this entry is ignored in following cases: + // 1) Array size is odd + // 2) Array contains negative numbers + // + // But what should we do, if we get 0? Pages in the PDF file are numbered from 1. + // So if this situation occur, we also ignore the entry. + const PDFArray* array = duplex.getArray(); + + const size_t count = array->getCount(); + if (count % 2 == 0 && count > 0) + { + bool badPageNumber = false; + int scanned = 0; + PDFInteger start = -1; + + for (size_t i = 0; i < count; ++i) + { + const PDFObject& number = document->getObject(array->getItem(i)); + if (number.isInt()) + { + PDFInteger current = number.getInteger(); + + if (current <= 0) + { + badPageNumber = true; + break; + } + + switch (scanned++) + { + case 0: + { + start = current; + break; + } + + case 1: + { + scanned = 0; + result.m_printPageRanges.emplace_back(start, current); + break; + } + + default: + Q_ASSERT(false); + } + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Expected integer.")); + } + } + + // Did we get negative or zero value? If yes, clear the range. + if (badPageNumber) + { + result.m_printPageRanges.clear(); + } + } + } + + // Number of copies + const PDFObject& numberOfCopies = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES)); + if (!numberOfCopies.isNull()) + { + if (numberOfCopies.isInt()) + { + result.m_numberOfCopies = numberOfCopies.getInteger(); + } + else + { + throw PDFParserException(PDFTranslationContext::tr("Expected integer.")); + } + } + } + else if (!viewerPreferencesObject.isNull()) + { + throw PDFParserException(PDFTranslationContext::tr("Viewer preferences must be a dictionary.")); + } + } + + return result; +} + +} // namespace pdf diff --git a/PdfForQtLib/sources/pdfcatalog.h b/PdfForQtLib/sources/pdfcatalog.h new file mode 100644 index 0000000..d3c0de5 --- /dev/null +++ b/PdfForQtLib/sources/pdfcatalog.h @@ -0,0 +1,162 @@ +// Copyright (C) 2018 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 PDFCATALOG_H +#define PDFCATALOG_H + +#include "pdfobject.h" + +#include + +#include +#include +#include + +namespace pdf +{ + +class PDFDocument; + +/// Defines page layout. Default value is SinglePage. This enum specifies the page layout +/// to be used in viewer application. +enum class PageLayout +{ + SinglePage, ///< Display one page at time (single page on screen) + OneColumn, ///< Displays pages in one column (continuous mode) + TwoColumnLeft, ///< Display pages in two continuous columns, odd numbered pages are on the left + TwoColumnRight, ///< Display pages in two continuous columns, even numbered pages are on the left + TwoPagesLeft, ///< Display two pages on the screen, odd numbered pages are on the left + TwoPagesRight ///< Display two pages on the screen, even numbered pages are on the left +}; + +/// Specifies, how the document should be displayed in the viewer application. +enum class PageMode +{ + UseNone, ///< Default value, neither document outline or thumbnails are visible + UseOutlines, ///< Document outline window is selected and visible + UseThumbnails, ///< Document thumbnails window is selected and visible + Fullscreen, ///< Use fullscreen mode, no menu bars, window controls, or any other window visible (presentation mode) + UseOptionalContent, ///< Optional content group window is selected and visible + UseAttachments, ///< Attachments window is selected and visible +}; + +class PDFViewerPreferences +{ +public: + + enum OptionFlag + { + None = 0x0000, ///< Empty flag + HideToolbar = 0x0001, ///< Hide toolbar + HideMenubar = 0x0002, ///< Hide menubar + HideWindowUI = 0x0004, ///< Hide window UI (for example scrollbars, navigation controls, etc.) + FitWindow = 0x0008, ///< Resize window to fit first displayed page + CenterWindow = 0x0010, ///< Position of the document's window should be centered on the screen + DisplayDocTitle = 0x0020, ///< Display documents title instead of file name (introduced in PDF 1.4) + PickTrayByPDFSize = 0x0040 ///< Pick tray by PDF size (printing option) + }; + + Q_DECLARE_FLAGS(OptionFlags, OptionFlag) + + /// This enum specifies, how to display document, when exiting full screen mode. + enum class NonFullScreenPageMode + { + UseNone, + UseOutline, + UseThumbnails, + UseOptionalContent + }; + + /// Predominant reading order of text. + enum class Direction + { + LeftToRight, ///< Default + RightToLeft ///< Reading order is right to left. Also used for vertical writing systems (Chinese/Japan etc.) + }; + + /// Printer settings - paper handling option to use when printing the document. + enum class Duplex + { + None, + Simplex, + DuplexFlipShortEdge, + DuplexFlipLongEdge + }; + + enum class PrintScaling + { + None, + AppDefault + }; + + enum Properties + { + ViewArea, + ViewClip, + PrintArea, + PrintClip, + EndProperties + }; + + constexpr inline PDFViewerPreferences() = default; + + constexpr inline PDFViewerPreferences(const PDFViewerPreferences&) = default; + constexpr inline PDFViewerPreferences(PDFViewerPreferences&&) = default; + + constexpr inline PDFViewerPreferences& operator=(const PDFViewerPreferences&) = default; + constexpr inline PDFViewerPreferences& operator=(PDFViewerPreferences&&) = default; + + /// Parses viewer preferences from catalog dictionary. If object cannot be parsed, or error occurs, + /// then exception is thrown. + static PDFViewerPreferences parse(const PDFObject& catalogDictionary, const PDFDocument* document); + +private: + OptionFlags m_optionFlags = None; + std::array m_properties; + NonFullScreenPageMode m_nonFullScreenPageMode = NonFullScreenPageMode::UseNone; + Direction m_direction = Direction::LeftToRight; + Duplex m_duplex = Duplex::None; + PrintScaling m_printScaling = PrintScaling::AppDefault; + std::vector> m_printPageRanges; + PDFInteger m_numberOfCopies = 1; +}; + +class PDFCatalog +{ +public: + constexpr inline PDFCatalog() = default; + + constexpr inline PDFCatalog(const PDFCatalog&) = default; + constexpr inline PDFCatalog(PDFCatalog&&) = default; + + constexpr inline PDFCatalog& operator=(const PDFCatalog&) = default; + constexpr inline PDFCatalog& operator=(PDFCatalog&&) = default; + + /// Returns viewer preferences of the application + const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; } + + /// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs, + /// then exception is thrown. + static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document); + +private: + PDFViewerPreferences m_viewerPreferences; +}; + +} // namespace pdf + +#endif // PDFCATALOG_H diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 4ea3d40..7a52c5f 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -110,6 +110,12 @@ public: /// Returns info about the document (title, author, etc.) const Info* getInfo() const { return &m_info; } + /// If object is reference, the dereference attempt is performed + /// and object is returned. If it is not a reference, then self + /// is returned. If dereference attempt fails, then null object + /// is returned (no exception is thrown). + const PDFObject& getObject(const PDFObject& object) const; + private: friend class PDFDocumentReader; @@ -128,12 +134,6 @@ private: /// info is used. If error is detected, exception is thrown. void initInfo(); - /// If object is reference, the dereference attempt is performed - /// and object is returned. If it is not a reference, then self - /// is returned. If dereference attempt fails, then null object - /// is returned (no exception is thrown). - const PDFObject& getObject(const PDFObject& object) const; - /// Storage of objects PDFObjectStorage m_pdfObjectStorage; diff --git a/PdfForQtLib/sources/pdfglobal.h b/PdfForQtLib/sources/pdfglobal.h index 7934a55..272a688 100644 --- a/PdfForQtLib/sources/pdfglobal.h +++ b/PdfForQtLib/sources/pdfglobal.h @@ -19,6 +19,7 @@ #ifndef PDFGLOBAL_H #define PDFGLOBAL_H +#include #include #include @@ -98,6 +99,11 @@ struct PDFVersion bool isValid() const { return major > 0; } }; +struct PDFTranslationContext +{ + Q_DECLARE_TR_FUNCTIONS(pdf::PDFTranslationContext) +}; + } // namespace pdf #endif // PDFGLOBAL_H