mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Text to speech engine implementation
This commit is contained in:
		| @@ -225,7 +225,8 @@ qt_libraries.files =    $$[QT_INSTALL_BINS]/Qt?Widgets$${SUFFIX}.dll \ | ||||
|                         $$[QT_INSTALL_BINS]/Qt?Core$${SUFFIX}.dll \ | ||||
|                         $$[QT_INSTALL_BINS]/Qt?WinExtras$${SUFFIX}.dll \ | ||||
|                         $$[QT_INSTALL_BINS]/Qt?Svg$${SUFFIX}.dll \ | ||||
|                         $$[QT_INSTALL_BINS]/Qt?PrintSupport$${SUFFIX}.dll | ||||
|                         $$[QT_INSTALL_BINS]/Qt?PrintSupport$${SUFFIX}.dll \ | ||||
|                         $$[QT_INSTALL_BINS]/Qt?TextToSpeech$${SUFFIX}.dll | ||||
| qt_libraries.path = $$DESTDIR/install | ||||
| INSTALLS += qt_libraries | ||||
|  | ||||
| @@ -240,3 +241,8 @@ INSTALLS += qt_plugin_iconengine | ||||
| qt_plugin_printsupport.files = $$[QT_INSTALL_PLUGINS]/printsupport/windowsprintersupport$${SUFFIX}.dll | ||||
| qt_plugin_printsupport.path = $$DESTDIR/install/printsupport | ||||
| INSTALLS += qt_plugin_printsupport | ||||
|  | ||||
| qt_plugin_texttospeech.files = $$[QT_INSTALL_PLUGINS]/texttospeech/qtexttospeech_sapi$${SUFFIX}.dll | ||||
| qt_plugin_texttospeech.path = $$DESTDIR/install/texttospeech | ||||
| INSTALLS += qt_plugin_texttospeech | ||||
|  | ||||
|   | ||||
| @@ -272,7 +272,7 @@ using PDFTextFlows = std::vector<PDFTextFlow>; | ||||
|  | ||||
| /// This class represents a portion of continuous text on the page. It can | ||||
| /// consists of multiple blocks (which follow reading order). | ||||
| class PDFTextFlow | ||||
| class PDFFORQTLIBSHARED_EXPORT PDFTextFlow | ||||
| { | ||||
| public: | ||||
|  | ||||
| @@ -294,6 +294,9 @@ public: | ||||
|     /// \param expression Regular expression to be matched | ||||
|     PDFFindResults find(const QRegularExpression& expression) const; | ||||
|  | ||||
|     /// Returns whole text for this text flow | ||||
|     QString getText() const { return m_text; } | ||||
|  | ||||
|     /// Returns text form character pointers | ||||
|     /// \param begin Begin character | ||||
|     /// \param end End character | ||||
| @@ -327,7 +330,7 @@ private: | ||||
|  | ||||
| /// Text layout of single page. Can handle various fonts, various angles of lines | ||||
| /// and vertically oriented text. It performs the "docstrum" algorithm. | ||||
| class PDFTextLayout | ||||
| class PDFFORQTLIBSHARED_EXPORT PDFTextLayout | ||||
| { | ||||
| public: | ||||
|     explicit PDFTextLayout(); | ||||
| @@ -498,4 +501,6 @@ private: | ||||
|  | ||||
| }   // namespace pdf | ||||
|  | ||||
| Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFTextFlow::FlowFlags) | ||||
|  | ||||
| #endif // PDFTEXTLAYOUT_H | ||||
|   | ||||
| @@ -445,6 +445,32 @@ inline QColor invertColor(QColor color) | ||||
|     return QColor::fromRgbF(r, g, b, a); | ||||
| } | ||||
|  | ||||
| /// Performs linear interpolation of interval [x1, x2] to interval [y1, y2], | ||||
| /// using formula y = y1 + (x - x1) * (y2 - y1) / (x2 - x1), transformed | ||||
| /// to formula y = k * x + q, where q = y1 - x1 * k and | ||||
| /// k = (y2 - y1) / (x2 - x1). | ||||
| template<typename T> | ||||
| class PDFLinearInterpolation | ||||
| { | ||||
| public: | ||||
|     constexpr inline PDFLinearInterpolation(T x1, T x2, T y1, T y2) : | ||||
|         m_k((y2 - y1) / (x2 - x1)), | ||||
|         m_q(y1 - x1 * m_k) | ||||
|     { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// Maps value from x interval to y interval | ||||
|     constexpr inline T operator()(T x) const | ||||
|     { | ||||
|         return m_k * x + m_q; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     T m_k; | ||||
|     T m_q; | ||||
| }; | ||||
|  | ||||
| }   // namespace pdf | ||||
|  | ||||
| #endif // PDFUTILS_H | ||||
|   | ||||
| @@ -41,6 +41,7 @@ SOURCES += \ | ||||
|         pdfrendertoimagesdialog.cpp \ | ||||
|         pdfsendmail.cpp \ | ||||
|         pdfsidebarwidget.cpp \ | ||||
|         pdftexttospeech.cpp \ | ||||
|         pdfviewermainwindow.cpp \ | ||||
|         pdfviewersettings.cpp \ | ||||
|         pdfviewersettingsdialog.cpp \ | ||||
| @@ -54,6 +55,7 @@ HEADERS += \ | ||||
|         pdfrendertoimagesdialog.h \ | ||||
|         pdfsendmail.h \ | ||||
|         pdfsidebarwidget.h \ | ||||
|         pdftexttospeech.h \ | ||||
|         pdfviewermainwindow.h \ | ||||
|         pdfviewersettings.h \ | ||||
|         pdfviewersettingsdialog.h \ | ||||
|   | ||||
| @@ -37,5 +37,8 @@ | ||||
|         <file>resources/rotate-right.svg</file> | ||||
|         <file>resources/print.svg</file> | ||||
|         <file>resources/speech.svg</file> | ||||
|         <file>resources/pause.svg</file> | ||||
|         <file>resources/play.svg</file> | ||||
|         <file>resources/stop.svg</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
| #include "ui_pdfsidebarwidget.h" | ||||
|  | ||||
| #include "pdfwidgetutils.h" | ||||
| #include "pdftexttospeech.h" | ||||
|  | ||||
| #include "pdfdocument.h" | ||||
| #include "pdfitemmodels.h" | ||||
| @@ -40,12 +41,14 @@ constexpr const char* STYLESHEET = | ||||
|         "QPushButton:disabled { background-color: #404040; color: #000000; }" | ||||
|         "QPushButton:checked { background-color: #808080; color: #FFFFFF; }" | ||||
|         "QWidget#thumbnailsToolbarWidget { background-color: #F0F0F0 }" | ||||
|         "QWidget#speechPage { background-color: #F0F0F0 }" | ||||
|         "QWidget#PDFSidebarWidget { background-color: #404040; background: green;}"; | ||||
|  | ||||
| PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* parent) : | ||||
| PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpeech* textToSpeech, QWidget* parent) : | ||||
|     QWidget(parent), | ||||
|     ui(new Ui::PDFSidebarWidget), | ||||
|     m_proxy(proxy), | ||||
|     m_textToSpeech(textToSpeech), | ||||
|     m_outlineTreeModel(nullptr), | ||||
|     m_thumbnailsModel(nullptr), | ||||
|     m_optionalContentTreeModel(nullptr), | ||||
| @@ -93,6 +96,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* pare | ||||
|     m_pageInfo[Bookmarks] = { ui->bookmarksButton, ui->bookmarksPage }; | ||||
|     m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage }; | ||||
|     m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage }; | ||||
|     m_pageInfo[Speech] = { ui->speechButton, ui->speechPage }; | ||||
|  | ||||
|     for (const auto& pageInfo : m_pageInfo) | ||||
|     { | ||||
| @@ -102,6 +106,10 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* pare | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     m_textToSpeech->initializeUI(ui->speechLocaleComboBox, ui->speechVoiceComboBox, | ||||
|                                  ui->speechRateEdit, ui->speechPitchEdit, ui->speechVolumeEdit, | ||||
|                                  ui->speechPlayButton, ui->speechPauseButton, ui->speechStopButton, ui->speechSynchronizeButton); | ||||
|  | ||||
|     selectPage(Invalid); | ||||
|     updateButtons(); | ||||
| } | ||||
| @@ -220,6 +228,9 @@ bool PDFSidebarWidget::isEmpty(Page page) const | ||||
|         case Attachments: | ||||
|             return m_attachmentsTreeModel->isEmpty(); | ||||
|  | ||||
|         case Speech: | ||||
|             return !m_textToSpeech->isValid(); | ||||
|  | ||||
|         default: | ||||
|             Q_ASSERT(false); | ||||
|             break; | ||||
|   | ||||
| @@ -45,13 +45,14 @@ class PDFOptionalContentTreeItemModel; | ||||
|  | ||||
| namespace pdfviewer | ||||
| { | ||||
| class PDFTextToSpeech; | ||||
|  | ||||
| class PDFSidebarWidget : public QWidget | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* parent = nullptr); | ||||
|     explicit PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpeech* textToSpeech, QWidget* parent); | ||||
|     virtual ~PDFSidebarWidget() override; | ||||
|  | ||||
|     virtual void paintEvent(QPaintEvent* event) override; | ||||
| @@ -64,6 +65,7 @@ public: | ||||
|         Thumbnails, | ||||
|         OptionalContent, | ||||
|         Attachments, | ||||
|         Speech, | ||||
|         _END | ||||
|     }; | ||||
|  | ||||
| @@ -105,6 +107,7 @@ private: | ||||
|  | ||||
|     Ui::PDFSidebarWidget* ui; | ||||
|     pdf::PDFDrawWidgetProxy* m_proxy; | ||||
|     PDFTextToSpeech* m_textToSpeech; | ||||
|     pdf::PDFOutlineTreeItemModel* m_outlineTreeModel; | ||||
|     pdf::PDFThumbnailsItemModel* m_thumbnailsModel; | ||||
|     pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel; | ||||
|   | ||||
| @@ -80,6 +80,19 @@ | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QPushButton" name="speechButton"> | ||||
|        <property name="text"> | ||||
|         <string>Speech</string> | ||||
|        </property> | ||||
|        <property name="checkable"> | ||||
|         <bool>true</bool> | ||||
|        </property> | ||||
|        <property name="flat"> | ||||
|         <bool>true</bool> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <spacer name="buttonSpacer"> | ||||
|        <property name="orientation"> | ||||
| @@ -98,7 +111,7 @@ | ||||
|    <item> | ||||
|     <widget class="QStackedWidget" name="stackedWidget"> | ||||
|      <property name="currentIndex"> | ||||
|       <number>2</number> | ||||
|       <number>5</number> | ||||
|      </property> | ||||
|      <widget class="QWidget" name="emptyPage"/> | ||||
|      <widget class="QWidget" name="bookmarksPage"> | ||||
| @@ -238,6 +251,142 @@ | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|      <widget class="QWidget" name="speechPage"> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|        <item> | ||||
|         <widget class="QComboBox" name="speechLocaleComboBox"/> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QComboBox" name="speechVoiceComboBox"/> | ||||
|        </item> | ||||
|        <item> | ||||
|         <layout class="QGridLayout" name="gridLayout"> | ||||
|          <item row="0" column="1"> | ||||
|           <widget class="QSlider" name="speechRateEdit"> | ||||
|            <property name="minimum"> | ||||
|             <number>-10</number> | ||||
|            </property> | ||||
|            <property name="maximum"> | ||||
|             <number>10</number> | ||||
|            </property> | ||||
|            <property name="orientation"> | ||||
|             <enum>Qt::Horizontal</enum> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item row="2" column="1"> | ||||
|           <widget class="QSlider" name="speechVolumeEdit"> | ||||
|            <property name="maximum"> | ||||
|             <number>100</number> | ||||
|            </property> | ||||
|            <property name="value"> | ||||
|             <number>100</number> | ||||
|            </property> | ||||
|            <property name="orientation"> | ||||
|             <enum>Qt::Horizontal</enum> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item row="1" column="1"> | ||||
|           <widget class="QSlider" name="speechPitchEdit"> | ||||
|            <property name="minimum"> | ||||
|             <number>-10</number> | ||||
|            </property> | ||||
|            <property name="maximum"> | ||||
|             <number>10</number> | ||||
|            </property> | ||||
|            <property name="orientation"> | ||||
|             <enum>Qt::Horizontal</enum> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item row="0" column="0"> | ||||
|           <widget class="QLabel" name="speechRateLabel"> | ||||
|            <property name="text"> | ||||
|             <string>Rate</string> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item row="1" column="0"> | ||||
|           <widget class="QLabel" name="speechPitchLabel"> | ||||
|            <property name="text"> | ||||
|             <string>Pitch</string> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item row="2" column="0"> | ||||
|           <widget class="QLabel" name="speechVolumeLabel"> | ||||
|            <property name="text"> | ||||
|             <string>Volume</string> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|         </layout> | ||||
|        </item> | ||||
|        <item> | ||||
|         <layout class="QHBoxLayout" name="horizontalLayout"> | ||||
|          <item> | ||||
|           <widget class="QToolButton" name="speechPlayButton"> | ||||
|            <property name="icon"> | ||||
|             <iconset resource="pdfforqtviewer.qrc"> | ||||
|              <normaloff>:/resources/play.svg</normaloff>:/resources/play.svg</iconset> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item> | ||||
|           <widget class="QToolButton" name="speechPauseButton"> | ||||
|            <property name="icon"> | ||||
|             <iconset resource="pdfforqtviewer.qrc"> | ||||
|              <normaloff>:/resources/pause.svg</normaloff>:/resources/pause.svg</iconset> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item> | ||||
|           <widget class="QToolButton" name="speechStopButton"> | ||||
|            <property name="icon"> | ||||
|             <iconset resource="pdfforqtviewer.qrc"> | ||||
|              <normaloff>:/resources/stop.svg</normaloff>:/resources/stop.svg</iconset> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item> | ||||
|           <spacer name="horizontalSpacer"> | ||||
|            <property name="orientation"> | ||||
|             <enum>Qt::Horizontal</enum> | ||||
|            </property> | ||||
|            <property name="sizeHint" stdset="0"> | ||||
|             <size> | ||||
|              <width>40</width> | ||||
|              <height>20</height> | ||||
|             </size> | ||||
|            </property> | ||||
|           </spacer> | ||||
|          </item> | ||||
|          <item> | ||||
|           <widget class="QToolButton" name="speechSynchronizeButton"> | ||||
|            <property name="icon"> | ||||
|             <iconset resource="pdfforqtviewer.qrc"> | ||||
|              <normaloff>:/resources/synchronize.svg</normaloff>:/resources/synchronize.svg</iconset> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|         </layout> | ||||
|        </item> | ||||
|        <item> | ||||
|         <spacer name="verticalSpacer"> | ||||
|          <property name="orientation"> | ||||
|           <enum>Qt::Vertical</enum> | ||||
|          </property> | ||||
|          <property name="sizeHint" stdset="0"> | ||||
|           <size> | ||||
|            <width>20</width> | ||||
|            <height>393</height> | ||||
|           </size> | ||||
|          </property> | ||||
|         </spacer> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|   | ||||
							
								
								
									
										498
									
								
								PdfForQtViewer/pdftexttospeech.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										498
									
								
								PdfForQtViewer/pdftexttospeech.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,498 @@ | ||||
| //    Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| #include "pdftexttospeech.h" | ||||
| #include "pdfviewersettings.h" | ||||
| #include "pdfdrawspacecontroller.h" | ||||
| #include "pdfcompiler.h" | ||||
| #include "pdfdrawwidget.h" | ||||
|  | ||||
| #include <QAction> | ||||
| #include <QSlider> | ||||
| #include <QComboBox> | ||||
| #include <QToolButton> | ||||
| #include <QTextToSpeech> | ||||
|  | ||||
| namespace pdfviewer | ||||
| { | ||||
|  | ||||
| PDFTextToSpeech::PDFTextToSpeech(QObject* parent) : | ||||
|     BaseClass(parent), | ||||
|     m_textToSpeech(nullptr), | ||||
|     m_document(nullptr), | ||||
|     m_proxy(nullptr), | ||||
|     m_state(Invalid), | ||||
|     m_initialized(false), | ||||
|     m_speechLocaleComboBox(nullptr), | ||||
|     m_speechVoiceComboBox(nullptr), | ||||
|     m_speechRateEdit(nullptr), | ||||
|     m_speechVolumeEdit(nullptr), | ||||
|     m_speechPitchEdit(nullptr), | ||||
|     m_speechPlayButton(nullptr), | ||||
|     m_speechPauseButton(nullptr), | ||||
|     m_speechStopButton(nullptr), | ||||
|     m_speechSynchronizeButton(nullptr) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| bool PDFTextToSpeech::isValid() const | ||||
| { | ||||
|     return m_document != nullptr; | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setDocument(const pdf::PDFDocument* document) | ||||
| { | ||||
|     if (m_document != document) | ||||
|     { | ||||
|         stop(); | ||||
|         m_document = document; | ||||
|  | ||||
|         if (m_textToSpeech) | ||||
|         { | ||||
|             m_state = m_document ? Ready : NoDocument; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Set state to invalid, speech engine is not set | ||||
|             m_state = Invalid; | ||||
|         } | ||||
|  | ||||
|         updateUI(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setSettings(const PDFViewerSettings* viewerSettings) | ||||
| { | ||||
|     Q_ASSERT(viewerSettings); | ||||
|  | ||||
|     if (!m_initialized) | ||||
|     { | ||||
|         // This object is not initialized yet | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // First, stop the engine | ||||
|     stop(); | ||||
|  | ||||
|     delete m_textToSpeech; | ||||
|     m_textToSpeech = nullptr; | ||||
|  | ||||
|     const PDFViewerSettings::Settings& settings = viewerSettings->getSettings(); | ||||
|     if (!settings.m_speechEngine.isEmpty()) | ||||
|     { | ||||
|         m_textToSpeech = new QTextToSpeech(settings.m_speechEngine); | ||||
|         connect(m_textToSpeech, &QTextToSpeech::stateChanged, this, &PDFTextToSpeech::updatePlay); | ||||
|         m_state = m_document ? Ready : NoDocument; | ||||
|  | ||||
|         QVector<QLocale> locales = m_textToSpeech->availableLocales(); | ||||
|         m_speechLocaleComboBox->setUpdatesEnabled(false); | ||||
|         m_speechLocaleComboBox->clear(); | ||||
|         for (const QLocale& locale : locales) | ||||
|         { | ||||
|             m_speechLocaleComboBox->addItem(QString("%1 (%2)").arg(locale.nativeLanguageName(), locale.nativeCountryName()), locale.name()); | ||||
|         } | ||||
|         m_speechLocaleComboBox->setUpdatesEnabled(true); | ||||
|  | ||||
|         QVector<QVoice> voices = m_textToSpeech->availableVoices(); | ||||
|         m_speechVoiceComboBox->setUpdatesEnabled(false); | ||||
|         m_speechVoiceComboBox->clear(); | ||||
|         for (const QVoice& voice : voices) | ||||
|         { | ||||
|             m_speechVoiceComboBox->addItem(QString("%1 (%2, %3)").arg(voice.name(), QVoice::genderName(voice.gender()), QVoice::ageName(voice.age())), voice.name()); | ||||
|         } | ||||
|         m_speechVoiceComboBox->setUpdatesEnabled(true); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         // Set state to invalid, speech engine is not set | ||||
|         m_state = Invalid; | ||||
|  | ||||
|         m_speechLocaleComboBox->clear(); | ||||
|         m_speechVoiceComboBox->clear(); | ||||
|     } | ||||
|  | ||||
|     if (m_textToSpeech) | ||||
|     { | ||||
|         setLocale(settings.m_speechLocale); | ||||
|         setVoice(settings.m_speechVoice); | ||||
|         setRate(settings.m_speechRate); | ||||
|         setPitch(settings.m_speechPitch); | ||||
|         setVolume(settings.m_speechVolume); | ||||
|     } | ||||
|  | ||||
|     updateUI(); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setProxy(const pdf::PDFDrawWidgetProxy* proxy) | ||||
| { | ||||
|     m_proxy = proxy; | ||||
|     pdf::PDFAsynchronousTextLayoutCompiler* compiler = m_proxy->getTextLayoutCompiler(); | ||||
|     connect(compiler, &pdf::PDFAsynchronousTextLayoutCompiler::textLayoutChanged, this, &PDFTextToSpeech::updatePlay); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::initializeUI(QComboBox* speechLocaleComboBox, | ||||
|                                    QComboBox* speechVoiceComboBox, | ||||
|                                    QSlider* speechRateEdit, | ||||
|                                    QSlider* speechVolumeEdit, | ||||
|                                    QSlider* speechPitchEdit, | ||||
|                                    QToolButton* speechPlayButton, | ||||
|                                    QToolButton* speechPauseButton, | ||||
|                                    QToolButton* speechStopButton, | ||||
|                                    QToolButton* speechSynchronizeButton) | ||||
| { | ||||
|     Q_ASSERT(speechLocaleComboBox); | ||||
|     Q_ASSERT(speechVoiceComboBox); | ||||
|     Q_ASSERT(speechRateEdit); | ||||
|     Q_ASSERT(speechVolumeEdit); | ||||
|     Q_ASSERT(speechPitchEdit); | ||||
|     Q_ASSERT(speechPlayButton); | ||||
|     Q_ASSERT(speechPauseButton); | ||||
|     Q_ASSERT(speechStopButton); | ||||
|     Q_ASSERT(speechSynchronizeButton); | ||||
|  | ||||
|     m_speechLocaleComboBox = speechLocaleComboBox; | ||||
|     m_speechVoiceComboBox = speechVoiceComboBox; | ||||
|     m_speechRateEdit = speechRateEdit; | ||||
|     m_speechVolumeEdit = speechVolumeEdit; | ||||
|     m_speechPitchEdit = speechPitchEdit; | ||||
|     m_speechPlayButton = speechPlayButton; | ||||
|     m_speechPauseButton = speechPauseButton; | ||||
|     m_speechStopButton = speechStopButton; | ||||
|     m_speechSynchronizeButton = speechSynchronizeButton; | ||||
|  | ||||
|     connect(m_speechLocaleComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PDFTextToSpeech::onLocaleChanged); | ||||
|     connect(m_speechVoiceComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PDFTextToSpeech::onVoiceChanged); | ||||
|     connect(m_speechRateEdit, &QSlider::valueChanged, this, &PDFTextToSpeech::onRateChanged); | ||||
|     connect(m_speechPitchEdit, &QSlider::valueChanged, this, &PDFTextToSpeech::onPitchChanged); | ||||
|     connect(m_speechVolumeEdit, &QSlider::valueChanged, this, &PDFTextToSpeech::onVolumeChanged); | ||||
|     connect(m_speechPlayButton, &QToolButton::clicked, this, &PDFTextToSpeech::onPlayClicked); | ||||
|     connect(m_speechPauseButton, &QToolButton::clicked, this, &PDFTextToSpeech::onPauseClicked); | ||||
|     connect(m_speechStopButton, &QToolButton::clicked, this, &PDFTextToSpeech::onStopClicked); | ||||
|  | ||||
|     m_initialized = true; | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::updateUI() | ||||
| { | ||||
|     bool enableControls = false; | ||||
|     bool enablePlay = false; | ||||
|     bool enablePause = false; | ||||
|     bool enableStop = false; | ||||
|  | ||||
|     switch (m_state) | ||||
|     { | ||||
|         case pdfviewer::PDFTextToSpeech::Invalid: | ||||
|         { | ||||
|             enableControls = false; | ||||
|             enablePlay = false; | ||||
|             enablePause = false; | ||||
|             enableStop = false; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case pdfviewer::PDFTextToSpeech::NoDocument: | ||||
|         { | ||||
|             enableControls = true; | ||||
|             enablePlay = false; | ||||
|             enablePause = false; | ||||
|             enableStop = false; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case pdfviewer::PDFTextToSpeech::Ready: | ||||
|         { | ||||
|             enableControls = true; | ||||
|             enablePlay = true; | ||||
|             enablePause = false; | ||||
|             enableStop = false; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case pdfviewer::PDFTextToSpeech::Playing: | ||||
|         { | ||||
|             enableControls = true; | ||||
|             enablePlay = false; | ||||
|             enablePause = true; | ||||
|             enableStop = true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case pdfviewer::PDFTextToSpeech::Paused: | ||||
|         { | ||||
|             enableControls = true; | ||||
|             enablePlay = true; | ||||
|             enablePause = false; | ||||
|             enableStop = true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case pdfviewer::PDFTextToSpeech::Error: | ||||
|         { | ||||
|             enableControls = false; | ||||
|             enablePlay = false; | ||||
|             enablePause = false; | ||||
|             enableStop = false; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         default: | ||||
|             Q_ASSERT(false); | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     m_speechLocaleComboBox->setEnabled(enableControls && m_speechLocaleComboBox->count() > 0); | ||||
|     m_speechVoiceComboBox->setEnabled(enableControls && m_speechVoiceComboBox->count() > 0); | ||||
|     m_speechRateEdit->setEnabled(enableControls); | ||||
|     m_speechVolumeEdit->setEnabled(enableControls); | ||||
|     m_speechPitchEdit->setEnabled(enableControls); | ||||
|     m_speechPlayButton->setEnabled(enablePlay); | ||||
|     m_speechPauseButton->setEnabled(enablePause); | ||||
|     m_speechStopButton->setEnabled(enableStop); | ||||
|     m_speechSynchronizeButton->setEnabled(enableControls); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::stop() | ||||
| { | ||||
|     switch (m_state) | ||||
|     { | ||||
|         case Playing: | ||||
|         case Paused: | ||||
|         { | ||||
|             m_textToSpeech->stop(); | ||||
|             m_currentTextFlowIndex = 0; | ||||
|             m_currentPage = 0; | ||||
|             m_currentTextLayout = pdf::PDFTextLayout(); | ||||
|             m_textFlows = pdf::PDFTextFlows(); | ||||
|             m_state = Ready; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     updateUI(); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setLocale(const QString& locale) | ||||
| { | ||||
|     m_speechLocaleComboBox->setCurrentIndex(m_speechLocaleComboBox->findData(locale)); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setVoice(const QString& voice) | ||||
| { | ||||
|     m_speechVoiceComboBox->setCurrentIndex(m_speechVoiceComboBox->findData(voice)); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setRate(const double rate) | ||||
| { | ||||
|     pdf::PDFLinearInterpolation<double> interpolation(-1.0, 1.0, m_speechRateEdit->minimum(), m_speechRateEdit->maximum()); | ||||
|     m_speechRateEdit->setValue(qRound(interpolation(rate))); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setPitch(const double pitch) | ||||
| { | ||||
|     pdf::PDFLinearInterpolation<double> interpolation(-1.0, 1.0, m_speechPitchEdit->minimum(), m_speechPitchEdit->maximum()); | ||||
|     m_speechPitchEdit->setValue(qRound(interpolation(pitch))); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::setVolume(const double volume) | ||||
| { | ||||
|     pdf::PDFLinearInterpolation<double> interpolation(0.0, 1.0, m_speechVolumeEdit->minimum(), m_speechVolumeEdit->maximum()); | ||||
|     m_speechVolumeEdit->setValue(qRound(interpolation(volume))); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onLocaleChanged() | ||||
| { | ||||
|     if (m_textToSpeech) | ||||
|     { | ||||
|         m_textToSpeech->setLocale(QLocale(m_speechLocaleComboBox->currentData().toString())); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onVoiceChanged() | ||||
| { | ||||
|     if (m_textToSpeech) | ||||
|     { | ||||
|         QString voice = m_speechVoiceComboBox->currentData().toString(); | ||||
|         for (const QVoice& voiceObject : m_textToSpeech->availableVoices()) | ||||
|         { | ||||
|             if (voiceObject.name() == voice) | ||||
|             { | ||||
|                 m_textToSpeech->setVoice(voiceObject); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onRateChanged(int rate) | ||||
| { | ||||
|     if (m_textToSpeech) | ||||
|     { | ||||
|         pdf::PDFLinearInterpolation<double> interpolation(m_speechRateEdit->minimum(), m_speechRateEdit->maximum(), -1.0, 1.0); | ||||
|         m_textToSpeech->setRate(interpolation(rate)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onPitchChanged(int pitch) | ||||
| { | ||||
|     if (m_textToSpeech) | ||||
|     { | ||||
|         pdf::PDFLinearInterpolation<double> interpolation(m_speechPitchEdit->minimum(), m_speechPitchEdit->maximum(), -1.0, 1.0); | ||||
|         m_textToSpeech->setPitch(interpolation(pitch)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onVolumeChanged(int volume) | ||||
| { | ||||
|     if (m_textToSpeech) | ||||
|     { | ||||
|         pdf::PDFLinearInterpolation<double> interpolation(m_speechVolumeEdit->minimum(), m_speechVolumeEdit->maximum(), 0.0, 1.0); | ||||
|         m_textToSpeech->setVolume(interpolation(volume)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onPlayClicked() | ||||
| { | ||||
|     switch (m_state) | ||||
|     { | ||||
|         case Paused: | ||||
|         { | ||||
|             m_textToSpeech->resume(); | ||||
|             m_state = Playing; | ||||
|             updatePlay(); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         case Ready: | ||||
|         { | ||||
|             m_state = Playing; | ||||
|             m_currentTextFlowIndex = std::numeric_limits<size_t>::max(); | ||||
|             m_currentPage = -1; | ||||
|             updatePlay(); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     updateUI(); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onPauseClicked() | ||||
| { | ||||
|     Q_ASSERT(m_state == Playing); | ||||
|  | ||||
|     if (m_state == Playing) | ||||
|     { | ||||
|         m_textToSpeech->pause(); | ||||
|         m_state = Paused; | ||||
|         updateUI(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::onStopClicked() | ||||
| { | ||||
|     stop(); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::updatePlay() | ||||
| { | ||||
|     if (m_state != Playing) | ||||
|     { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Q_ASSERT(m_proxy); | ||||
|     Q_ASSERT(m_document); | ||||
|  | ||||
|     // Jakub Melka: Check, if we have text layout. If not, then create it and return immediately. | ||||
|     // Otherwise, check, if we have something to say. | ||||
|     pdf::PDFAsynchronousTextLayoutCompiler* compiler = m_proxy->getTextLayoutCompiler(); | ||||
|     if (!compiler->isTextLayoutReady()) | ||||
|     { | ||||
|         compiler->makeTextLayout(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     QTextToSpeech::State state = m_textToSpeech->state(); | ||||
|     if (state == QTextToSpeech::Ready) | ||||
|     { | ||||
|         if (m_currentPage == -1) | ||||
|         { | ||||
|             // Handle starting of document reading | ||||
|             std::vector<pdf::PDFInteger> currentPages = m_proxy->getWidget()->getDrawWidget()->getCurrentPages(); | ||||
|             if (!currentPages.empty()) | ||||
|             { | ||||
|                 updateToNextPage(currentPages.front()); | ||||
|             } | ||||
|         } | ||||
|         else if (++m_currentTextFlowIndex >= m_textFlows.size()) | ||||
|         { | ||||
|             // Handle transition to next page | ||||
|             updateToNextPage(m_currentPage + 1); | ||||
|         } | ||||
|  | ||||
|         if (m_currentTextFlowIndex < m_textFlows.size()) | ||||
|         { | ||||
|             // Say next thing | ||||
|             const pdf::PDFTextFlow& textFlow = m_textFlows[m_currentTextFlowIndex]; | ||||
|             QString text = textFlow.getText(); | ||||
|             m_textToSpeech->say(text); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // We are finished the reading | ||||
|             m_state = Ready; | ||||
|         } | ||||
|     } | ||||
|     else if (state == QTextToSpeech::BackendError) | ||||
|     { | ||||
|         m_state = Error; | ||||
|     } | ||||
|  | ||||
|     updateUI(); | ||||
| } | ||||
|  | ||||
| void PDFTextToSpeech::updateToNextPage(pdf::PDFInteger pageIndex) | ||||
| { | ||||
|     Q_ASSERT(m_document); | ||||
|     Q_ASSERT(m_state = Playing); | ||||
|  | ||||
|     m_currentPage = pageIndex; | ||||
|     const pdf::PDFInteger pageCount = m_document->getCatalog()->getPageCount(); | ||||
|  | ||||
|     pdf::PDFAsynchronousTextLayoutCompiler* compiler = m_proxy->getTextLayoutCompiler(); | ||||
|     Q_ASSERT(compiler->isTextLayoutReady()); | ||||
|  | ||||
|     // Find first nonempty page | ||||
|     while (m_currentPage < pageCount) | ||||
|     { | ||||
|         m_currentTextLayout = compiler->getTextLayout(m_currentPage); | ||||
|         m_textFlows = pdf::PDFTextFlow::createTextFlows(m_currentTextLayout, pdf::PDFTextFlow::SeparateBlocks | pdf::PDFTextFlow::RemoveSoftHyphen, m_currentPage); | ||||
|  | ||||
|         if (!m_textFlows.empty()) | ||||
|         { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         ++m_currentPage; | ||||
|     } | ||||
|  | ||||
|     m_currentTextFlowIndex = 0; | ||||
| } | ||||
|  | ||||
| }   // namespace pdfviewer | ||||
							
								
								
									
										136
									
								
								PdfForQtViewer/pdftexttospeech.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								PdfForQtViewer/pdftexttospeech.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| //    Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. | ||||
|  | ||||
|  | ||||
| #ifndef PDFTEXTTOSPEECH_H | ||||
| #define PDFTEXTTOSPEECH_H | ||||
|  | ||||
| #include "pdftextlayout.h" | ||||
|  | ||||
| #include <QObject> | ||||
|  | ||||
| class QSlider; | ||||
| class QComboBox; | ||||
| class QToolButton; | ||||
| class QTextToSpeech; | ||||
|  | ||||
| namespace pdf | ||||
| { | ||||
| class PDFDocument; | ||||
| class PDFDrawWidgetProxy; | ||||
| } | ||||
|  | ||||
| namespace pdfviewer | ||||
| { | ||||
| class PDFViewerSettings; | ||||
|  | ||||
| /// Text to speech engine used to reading the document | ||||
| class PDFTextToSpeech : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| private: | ||||
|     using BaseClass = QObject; | ||||
|  | ||||
| public: | ||||
|     explicit PDFTextToSpeech(QObject* parent); | ||||
|  | ||||
|     enum State | ||||
|     { | ||||
|         Invalid,    ///< Text to speech engine is invalid (maybe bad engine) | ||||
|         NoDocument, ///< No document to read | ||||
|         Ready,      ///< Ready to read the document contents | ||||
|         Playing,    ///< Document is being read | ||||
|         Paused,     ///< User paused the reading | ||||
|         Error       ///< Error occured in text to speech engine | ||||
|     }; | ||||
|  | ||||
|     /// Returns true, if text to speech engine is valid and can be used | ||||
|     /// to synthetise text. | ||||
|     bool isValid() const; | ||||
|  | ||||
|     /// Sets active document to text to speech engine | ||||
|     void setDocument(const pdf::PDFDocument* document); | ||||
|  | ||||
|     /// Apply settings to the reader | ||||
|     void setSettings(const PDFViewerSettings* viewerSettings); | ||||
|  | ||||
|     /// Set draw proxy | ||||
|     void setProxy(const pdf::PDFDrawWidgetProxy* proxy); | ||||
|  | ||||
|     /// Initialize the ui, which is used | ||||
|     void initializeUI(QComboBox* speechLocaleComboBox, | ||||
|                       QComboBox* speechVoiceComboBox, | ||||
|                       QSlider* speechRateEdit, | ||||
|                       QSlider* speechVolumeEdit, | ||||
|                       QSlider* speechPitchEdit, | ||||
|                       QToolButton* speechPlayButton, | ||||
|                       QToolButton* speechPauseButton, | ||||
|                       QToolButton* speechStopButton, | ||||
|                       QToolButton* speechSynchronizeButton); | ||||
|  | ||||
| private: | ||||
|     /// Updates UI controls depending on the state | ||||
|     void updateUI(); | ||||
|  | ||||
|     /// Stop the engine, if it is reading | ||||
|     void stop(); | ||||
|  | ||||
|     void setLocale(const QString& locale); | ||||
|     void setVoice(const QString& voice); | ||||
|     void setRate(const double rate); | ||||
|     void setPitch(const double pitch); | ||||
|     void setVolume(const double volume); | ||||
|  | ||||
|     void onLocaleChanged(); | ||||
|     void onVoiceChanged(); | ||||
|     void onRateChanged(int rate); | ||||
|     void onPitchChanged(int pitch); | ||||
|     void onVolumeChanged(int volume); | ||||
|  | ||||
|     void onPlayClicked(); | ||||
|     void onPauseClicked(); | ||||
|     void onStopClicked(); | ||||
|  | ||||
|     void updatePlay(); | ||||
|     void updateToNextPage(pdf::PDFInteger pageIndex); | ||||
|  | ||||
|     QTextToSpeech* m_textToSpeech; | ||||
|     const pdf::PDFDocument* m_document; | ||||
|     const pdf::PDFDrawWidgetProxy* m_proxy; | ||||
|     State m_state; | ||||
|     bool m_initialized; | ||||
|  | ||||
|     QComboBox* m_speechLocaleComboBox; | ||||
|     QComboBox* m_speechVoiceComboBox; | ||||
|     QSlider* m_speechRateEdit; | ||||
|     QSlider* m_speechVolumeEdit; | ||||
|     QSlider* m_speechPitchEdit; | ||||
|     QToolButton* m_speechPlayButton; | ||||
|     QToolButton* m_speechPauseButton; | ||||
|     QToolButton* m_speechStopButton; | ||||
|     QToolButton* m_speechSynchronizeButton; | ||||
|  | ||||
|     pdf::PDFTextLayout m_currentTextLayout; ///< Text layout for actual page | ||||
|     pdf::PDFTextFlows m_textFlows; ///< Text flows for actual page | ||||
|     size_t m_currentTextFlowIndex = 0; ///< Index of current text flow | ||||
|     pdf::PDFInteger m_currentPage = 0; ///< Current page | ||||
| }; | ||||
|  | ||||
| }   // namespace pdfviewer | ||||
|  | ||||
| #endif // PDFTEXTTOSPEECH_H | ||||
| @@ -88,7 +88,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : | ||||
|     m_progressDialog(nullptr), | ||||
|     m_isBusy(false), | ||||
|     m_isChangingProgressStep(false), | ||||
|     m_toolManager(nullptr) | ||||
|     m_toolManager(nullptr), | ||||
|     m_textToSpeech(new PDFTextToSpeech(this)) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
|  | ||||
| @@ -199,8 +200,9 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : | ||||
|     setFocusProxy(m_pdfWidget); | ||||
|     m_pdfWidget->updateCacheLimits(m_settings->getCompiledPageCacheLimit() * 1024, m_settings->getThumbnailsCacheLimit(), m_settings->getFontCacheLimit(), m_settings->getInstancedFontCacheLimit()); | ||||
|     m_pdfWidget->getDrawWidgetProxy()->setProgress(m_progress); | ||||
|     m_textToSpeech->setProxy(m_pdfWidget->getDrawWidgetProxy()); | ||||
|  | ||||
|     m_sidebarWidget = new PDFSidebarWidget(m_pdfWidget->getDrawWidgetProxy(), this); | ||||
|     m_sidebarWidget = new PDFSidebarWidget(m_pdfWidget->getDrawWidgetProxy(), m_textToSpeech, this); | ||||
|     m_sidebarDockWidget = new QDockWidget(tr("Sidebar"), this); | ||||
|     m_sidebarDockWidget->setObjectName("SidebarDockWidget"); | ||||
|     m_sidebarDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); | ||||
| @@ -208,6 +210,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : | ||||
|     addDockWidget(Qt::LeftDockWidgetArea, m_sidebarDockWidget); | ||||
|     m_sidebarDockWidget->hide(); | ||||
|     connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, this, &PDFViewerMainWindow::onActionTriggered); | ||||
|     m_textToSpeech->setSettings(m_settings); | ||||
|  | ||||
|     m_advancedFindWidget = new PDFAdvancedFindWidget(m_pdfWidget->getDrawWidgetProxy(), this); | ||||
|     m_advancedFindDockWidget = new QDockWidget(tr("Advanced find"), this); | ||||
| @@ -648,6 +651,7 @@ void PDFViewerMainWindow::readSettings() | ||||
|  | ||||
|     m_settings->readSettings(settings, m_CMSManager->getDefaultSettings()); | ||||
|     m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings()); | ||||
|     m_textToSpeech->setSettings(m_settings); | ||||
| } | ||||
|  | ||||
| void PDFViewerMainWindow::readActionSettings() | ||||
| @@ -956,6 +960,7 @@ void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document) | ||||
|     } | ||||
|  | ||||
|     m_toolManager->setDocument(document); | ||||
|     m_textToSpeech->setDocument(document); | ||||
|     m_pdfWidget->setDocument(document, m_optionalContentActivity); | ||||
|     m_sidebarWidget->setDocument(document, m_optionalContentActivity); | ||||
|     m_advancedFindWidget->setDocument(document); | ||||
| @@ -1130,6 +1135,7 @@ void PDFViewerMainWindow::on_actionOptions_triggered() | ||||
|         m_settings->setColorManagementSystemSettings(dialog.getCMSSettings()); | ||||
|         m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings()); | ||||
|         m_recentFileManager->setRecentFilesLimit(dialog.getOtherSettings().maximumRecentFileCount); | ||||
|         m_textToSpeech->setSettings(m_settings); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,7 @@ | ||||
| #include "pdfdocumentpropertiesdialog.h" | ||||
| #include "pdfwidgettool.h" | ||||
| #include "pdfrecentfilemanager.h" | ||||
| #include "pdftexttospeech.h" | ||||
|  | ||||
| #include <QFuture> | ||||
| #include <QTreeView> | ||||
| @@ -177,6 +178,7 @@ private: | ||||
|     bool m_isChangingProgressStep; | ||||
|  | ||||
|     pdf::PDFToolManager* m_toolManager; | ||||
|     PDFTextToSpeech* m_textToSpeech; | ||||
| }; | ||||
|  | ||||
| }   // namespace pdfviewer | ||||
|   | ||||
							
								
								
									
										93
									
								
								PdfForQtViewer/resources/pause.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								PdfForQtViewer/resources/pause.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="30mm" | ||||
|    height="30mm" | ||||
|    viewBox="0 0 30 30" | ||||
|    version="1.1" | ||||
|    id="svg5291" | ||||
|    inkscape:version="0.92.4 (5da689c313, 2019-01-14)" | ||||
|    sodipodi:docname="pause.svg"> | ||||
|   <defs | ||||
|      id="defs5285" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="11.2" | ||||
|      inkscape:cx="40.308699" | ||||
|      inkscape:cy="60.048176" | ||||
|      inkscape:document-units="mm" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="false" | ||||
|      inkscape:window-width="3840" | ||||
|      inkscape:window-height="2035" | ||||
|      inkscape:window-x="-13" | ||||
|      inkscape:window-y="-13" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata5288"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|         <dc:creator> | ||||
|           <cc:Agent> | ||||
|             <dc:title>Jakub Melka</dc:title> | ||||
|           </cc:Agent> | ||||
|         </dc:creator> | ||||
|         <cc:license | ||||
|            rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /> | ||||
|       </cc:Work> | ||||
|       <cc:License | ||||
|          rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#Reproduction" /> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#Distribution" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#Notice" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#Attribution" /> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#ShareAlike" /> | ||||
|       </cc:License> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Vrstva 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1" | ||||
|      transform="translate(0,-267)"> | ||||
|     <rect | ||||
|        style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.36963582;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" | ||||
|        id="rect4530" | ||||
|        width="4.6841955" | ||||
|        height="23.677502" | ||||
|        x="6.920681" | ||||
|        y="269.88867" /> | ||||
|     <rect | ||||
|        style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.36963594;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" | ||||
|        id="rect4530-3" | ||||
|        width="4.6841955" | ||||
|        height="23.677502" | ||||
|        x="18.016645" | ||||
|        y="269.91937" /> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										84
									
								
								PdfForQtViewer/resources/play.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								PdfForQtViewer/resources/play.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="30mm" | ||||
|    height="30mm" | ||||
|    viewBox="0 0 30 30" | ||||
|    version="1.1" | ||||
|    id="svg5291" | ||||
|    inkscape:version="0.92.4 (5da689c313, 2019-01-14)" | ||||
|    sodipodi:docname="play.svg"> | ||||
|   <defs | ||||
|      id="defs5285" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="11.2" | ||||
|      inkscape:cx="39.058699" | ||||
|      inkscape:cy="60.048176" | ||||
|      inkscape:document-units="mm" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="false" | ||||
|      inkscape:window-width="3840" | ||||
|      inkscape:window-height="2035" | ||||
|      inkscape:window-x="-13" | ||||
|      inkscape:window-y="-13" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata5288"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|         <dc:creator> | ||||
|           <cc:Agent> | ||||
|             <dc:title>Jakub Melka</dc:title> | ||||
|           </cc:Agent> | ||||
|         </dc:creator> | ||||
|         <cc:license | ||||
|            rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /> | ||||
|       </cc:Work> | ||||
|       <cc:License | ||||
|          rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#Reproduction" /> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#Distribution" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#Notice" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#Attribution" /> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#ShareAlike" /> | ||||
|       </cc:License> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Vrstva 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1" | ||||
|      transform="translate(0,-267)"> | ||||
|     <path | ||||
|        style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        d="M 9.6856398,269.90383 9.9218749,293.97619 20.174479,281.5266 Z" | ||||
|        id="path5295" | ||||
|        inkscape:connector-curvature="0" /> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										93
									
								
								PdfForQtViewer/resources/stop.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								PdfForQtViewer/resources/stop.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="30mm" | ||||
|    height="30mm" | ||||
|    viewBox="0 0 30 30" | ||||
|    version="1.1" | ||||
|    id="svg5291" | ||||
|    inkscape:version="0.92.4 (5da689c313, 2019-01-14)" | ||||
|    sodipodi:docname="stop.svg"> | ||||
|   <defs | ||||
|      id="defs5285" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="11.2" | ||||
|      inkscape:cx="41.558699" | ||||
|      inkscape:cy="60.048176" | ||||
|      inkscape:document-units="mm" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="false" | ||||
|      inkscape:window-width="3840" | ||||
|      inkscape:window-height="2035" | ||||
|      inkscape:window-x="-13" | ||||
|      inkscape:window-y="-13" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata5288"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|         <dc:creator> | ||||
|           <cc:Agent> | ||||
|             <dc:title>Jakub Melka</dc:title> | ||||
|           </cc:Agent> | ||||
|         </dc:creator> | ||||
|         <cc:license | ||||
|            rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /> | ||||
|       </cc:Work> | ||||
|       <cc:License | ||||
|          rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#Reproduction" /> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#Distribution" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#Notice" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#Attribution" /> | ||||
|         <cc:permits | ||||
|            rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> | ||||
|         <cc:requires | ||||
|            rdf:resource="http://creativecommons.org/ns#ShareAlike" /> | ||||
|       </cc:License> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Vrstva 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1" | ||||
|      transform="translate(0,-267)"> | ||||
|     <rect | ||||
|        style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2.7220552;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" | ||||
|        id="rect4530" | ||||
|        width="19.48431" | ||||
|        height="22.483788" | ||||
|        x="5.4143338" | ||||
|        y="270.4855" /> | ||||
|     <rect | ||||
|        style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.36963594;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" | ||||
|        id="rect4530-3" | ||||
|        width="4.6841955" | ||||
|        height="23.677502" | ||||
|        x="18.016645" | ||||
|        y="269.91937" /> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 3.1 KiB | 
		Reference in New Issue
	
	Block a user