mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-03-19 21:00:21 +01:00
Text to speech engine implementation
This commit is contained in:
parent
7df807470b
commit
ca9e2f3149
@ -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?Core$${SUFFIX}.dll \
|
||||||
$$[QT_INSTALL_BINS]/Qt?WinExtras$${SUFFIX}.dll \
|
$$[QT_INSTALL_BINS]/Qt?WinExtras$${SUFFIX}.dll \
|
||||||
$$[QT_INSTALL_BINS]/Qt?Svg$${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
|
qt_libraries.path = $$DESTDIR/install
|
||||||
INSTALLS += qt_libraries
|
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.files = $$[QT_INSTALL_PLUGINS]/printsupport/windowsprintersupport$${SUFFIX}.dll
|
||||||
qt_plugin_printsupport.path = $$DESTDIR/install/printsupport
|
qt_plugin_printsupport.path = $$DESTDIR/install/printsupport
|
||||||
INSTALLS += qt_plugin_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
|
/// This class represents a portion of continuous text on the page. It can
|
||||||
/// consists of multiple blocks (which follow reading order).
|
/// consists of multiple blocks (which follow reading order).
|
||||||
class PDFTextFlow
|
class PDFFORQTLIBSHARED_EXPORT PDFTextFlow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -294,6 +294,9 @@ public:
|
|||||||
/// \param expression Regular expression to be matched
|
/// \param expression Regular expression to be matched
|
||||||
PDFFindResults find(const QRegularExpression& expression) const;
|
PDFFindResults find(const QRegularExpression& expression) const;
|
||||||
|
|
||||||
|
/// Returns whole text for this text flow
|
||||||
|
QString getText() const { return m_text; }
|
||||||
|
|
||||||
/// Returns text form character pointers
|
/// Returns text form character pointers
|
||||||
/// \param begin Begin character
|
/// \param begin Begin character
|
||||||
/// \param end End character
|
/// \param end End character
|
||||||
@ -327,7 +330,7 @@ private:
|
|||||||
|
|
||||||
/// Text layout of single page. Can handle various fonts, various angles of lines
|
/// Text layout of single page. Can handle various fonts, various angles of lines
|
||||||
/// and vertically oriented text. It performs the "docstrum" algorithm.
|
/// and vertically oriented text. It performs the "docstrum" algorithm.
|
||||||
class PDFTextLayout
|
class PDFFORQTLIBSHARED_EXPORT PDFTextLayout
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit PDFTextLayout();
|
explicit PDFTextLayout();
|
||||||
@ -498,4 +501,6 @@ private:
|
|||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFTextFlow::FlowFlags)
|
||||||
|
|
||||||
#endif // PDFTEXTLAYOUT_H
|
#endif // PDFTEXTLAYOUT_H
|
||||||
|
@ -445,6 +445,32 @@ inline QColor invertColor(QColor color)
|
|||||||
return QColor::fromRgbF(r, g, b, a);
|
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
|
} // namespace pdf
|
||||||
|
|
||||||
#endif // PDFUTILS_H
|
#endif // PDFUTILS_H
|
||||||
|
@ -41,6 +41,7 @@ SOURCES += \
|
|||||||
pdfrendertoimagesdialog.cpp \
|
pdfrendertoimagesdialog.cpp \
|
||||||
pdfsendmail.cpp \
|
pdfsendmail.cpp \
|
||||||
pdfsidebarwidget.cpp \
|
pdfsidebarwidget.cpp \
|
||||||
|
pdftexttospeech.cpp \
|
||||||
pdfviewermainwindow.cpp \
|
pdfviewermainwindow.cpp \
|
||||||
pdfviewersettings.cpp \
|
pdfviewersettings.cpp \
|
||||||
pdfviewersettingsdialog.cpp \
|
pdfviewersettingsdialog.cpp \
|
||||||
@ -54,6 +55,7 @@ HEADERS += \
|
|||||||
pdfrendertoimagesdialog.h \
|
pdfrendertoimagesdialog.h \
|
||||||
pdfsendmail.h \
|
pdfsendmail.h \
|
||||||
pdfsidebarwidget.h \
|
pdfsidebarwidget.h \
|
||||||
|
pdftexttospeech.h \
|
||||||
pdfviewermainwindow.h \
|
pdfviewermainwindow.h \
|
||||||
pdfviewersettings.h \
|
pdfviewersettings.h \
|
||||||
pdfviewersettingsdialog.h \
|
pdfviewersettingsdialog.h \
|
||||||
|
@ -37,5 +37,8 @@
|
|||||||
<file>resources/rotate-right.svg</file>
|
<file>resources/rotate-right.svg</file>
|
||||||
<file>resources/print.svg</file>
|
<file>resources/print.svg</file>
|
||||||
<file>resources/speech.svg</file>
|
<file>resources/speech.svg</file>
|
||||||
|
<file>resources/pause.svg</file>
|
||||||
|
<file>resources/play.svg</file>
|
||||||
|
<file>resources/stop.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "ui_pdfsidebarwidget.h"
|
#include "ui_pdfsidebarwidget.h"
|
||||||
|
|
||||||
#include "pdfwidgetutils.h"
|
#include "pdfwidgetutils.h"
|
||||||
|
#include "pdftexttospeech.h"
|
||||||
|
|
||||||
#include "pdfdocument.h"
|
#include "pdfdocument.h"
|
||||||
#include "pdfitemmodels.h"
|
#include "pdfitemmodels.h"
|
||||||
@ -40,12 +41,14 @@ constexpr const char* STYLESHEET =
|
|||||||
"QPushButton:disabled { background-color: #404040; color: #000000; }"
|
"QPushButton:disabled { background-color: #404040; color: #000000; }"
|
||||||
"QPushButton:checked { background-color: #808080; color: #FFFFFF; }"
|
"QPushButton:checked { background-color: #808080; color: #FFFFFF; }"
|
||||||
"QWidget#thumbnailsToolbarWidget { background-color: #F0F0F0 }"
|
"QWidget#thumbnailsToolbarWidget { background-color: #F0F0F0 }"
|
||||||
|
"QWidget#speechPage { background-color: #F0F0F0 }"
|
||||||
"QWidget#PDFSidebarWidget { background-color: #404040; background: green;}";
|
"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),
|
QWidget(parent),
|
||||||
ui(new Ui::PDFSidebarWidget),
|
ui(new Ui::PDFSidebarWidget),
|
||||||
m_proxy(proxy),
|
m_proxy(proxy),
|
||||||
|
m_textToSpeech(textToSpeech),
|
||||||
m_outlineTreeModel(nullptr),
|
m_outlineTreeModel(nullptr),
|
||||||
m_thumbnailsModel(nullptr),
|
m_thumbnailsModel(nullptr),
|
||||||
m_optionalContentTreeModel(nullptr),
|
m_optionalContentTreeModel(nullptr),
|
||||||
@ -93,6 +96,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* pare
|
|||||||
m_pageInfo[Bookmarks] = { ui->bookmarksButton, ui->bookmarksPage };
|
m_pageInfo[Bookmarks] = { ui->bookmarksButton, ui->bookmarksPage };
|
||||||
m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage };
|
m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage };
|
||||||
m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage };
|
m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage };
|
||||||
|
m_pageInfo[Speech] = { ui->speechButton, ui->speechPage };
|
||||||
|
|
||||||
for (const auto& pageInfo : m_pageInfo)
|
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);
|
selectPage(Invalid);
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
@ -220,6 +228,9 @@ bool PDFSidebarWidget::isEmpty(Page page) const
|
|||||||
case Attachments:
|
case Attachments:
|
||||||
return m_attachmentsTreeModel->isEmpty();
|
return m_attachmentsTreeModel->isEmpty();
|
||||||
|
|
||||||
|
case Speech:
|
||||||
|
return !m_textToSpeech->isValid();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Q_ASSERT(false);
|
Q_ASSERT(false);
|
||||||
break;
|
break;
|
||||||
|
@ -45,13 +45,14 @@ class PDFOptionalContentTreeItemModel;
|
|||||||
|
|
||||||
namespace pdfviewer
|
namespace pdfviewer
|
||||||
{
|
{
|
||||||
|
class PDFTextToSpeech;
|
||||||
|
|
||||||
class PDFSidebarWidget : public QWidget
|
class PDFSidebarWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* parent = nullptr);
|
explicit PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpeech* textToSpeech, QWidget* parent);
|
||||||
virtual ~PDFSidebarWidget() override;
|
virtual ~PDFSidebarWidget() override;
|
||||||
|
|
||||||
virtual void paintEvent(QPaintEvent* event) override;
|
virtual void paintEvent(QPaintEvent* event) override;
|
||||||
@ -64,6 +65,7 @@ public:
|
|||||||
Thumbnails,
|
Thumbnails,
|
||||||
OptionalContent,
|
OptionalContent,
|
||||||
Attachments,
|
Attachments,
|
||||||
|
Speech,
|
||||||
_END
|
_END
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,6 +107,7 @@ private:
|
|||||||
|
|
||||||
Ui::PDFSidebarWidget* ui;
|
Ui::PDFSidebarWidget* ui;
|
||||||
pdf::PDFDrawWidgetProxy* m_proxy;
|
pdf::PDFDrawWidgetProxy* m_proxy;
|
||||||
|
PDFTextToSpeech* m_textToSpeech;
|
||||||
pdf::PDFOutlineTreeItemModel* m_outlineTreeModel;
|
pdf::PDFOutlineTreeItemModel* m_outlineTreeModel;
|
||||||
pdf::PDFThumbnailsItemModel* m_thumbnailsModel;
|
pdf::PDFThumbnailsItemModel* m_thumbnailsModel;
|
||||||
pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel;
|
pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel;
|
||||||
|
@ -80,6 +80,19 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<spacer name="buttonSpacer">
|
<spacer name="buttonSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -98,7 +111,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="stackedWidget">
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>2</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="emptyPage"/>
|
<widget class="QWidget" name="emptyPage"/>
|
||||||
<widget class="QWidget" name="bookmarksPage">
|
<widget class="QWidget" name="bookmarksPage">
|
||||||
@ -238,6 +251,142 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</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_progressDialog(nullptr),
|
||||||
m_isBusy(false),
|
m_isBusy(false),
|
||||||
m_isChangingProgressStep(false),
|
m_isChangingProgressStep(false),
|
||||||
m_toolManager(nullptr)
|
m_toolManager(nullptr),
|
||||||
|
m_textToSpeech(new PDFTextToSpeech(this))
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
@ -199,8 +200,9 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
setFocusProxy(m_pdfWidget);
|
setFocusProxy(m_pdfWidget);
|
||||||
m_pdfWidget->updateCacheLimits(m_settings->getCompiledPageCacheLimit() * 1024, m_settings->getThumbnailsCacheLimit(), m_settings->getFontCacheLimit(), m_settings->getInstancedFontCacheLimit());
|
m_pdfWidget->updateCacheLimits(m_settings->getCompiledPageCacheLimit() * 1024, m_settings->getThumbnailsCacheLimit(), m_settings->getFontCacheLimit(), m_settings->getInstancedFontCacheLimit());
|
||||||
m_pdfWidget->getDrawWidgetProxy()->setProgress(m_progress);
|
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 = new QDockWidget(tr("Sidebar"), this);
|
||||||
m_sidebarDockWidget->setObjectName("SidebarDockWidget");
|
m_sidebarDockWidget->setObjectName("SidebarDockWidget");
|
||||||
m_sidebarDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
m_sidebarDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||||
@ -208,6 +210,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
addDockWidget(Qt::LeftDockWidgetArea, m_sidebarDockWidget);
|
addDockWidget(Qt::LeftDockWidgetArea, m_sidebarDockWidget);
|
||||||
m_sidebarDockWidget->hide();
|
m_sidebarDockWidget->hide();
|
||||||
connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, this, &PDFViewerMainWindow::onActionTriggered);
|
connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, this, &PDFViewerMainWindow::onActionTriggered);
|
||||||
|
m_textToSpeech->setSettings(m_settings);
|
||||||
|
|
||||||
m_advancedFindWidget = new PDFAdvancedFindWidget(m_pdfWidget->getDrawWidgetProxy(), this);
|
m_advancedFindWidget = new PDFAdvancedFindWidget(m_pdfWidget->getDrawWidgetProxy(), this);
|
||||||
m_advancedFindDockWidget = new QDockWidget(tr("Advanced find"), this);
|
m_advancedFindDockWidget = new QDockWidget(tr("Advanced find"), this);
|
||||||
@ -648,6 +651,7 @@ void PDFViewerMainWindow::readSettings()
|
|||||||
|
|
||||||
m_settings->readSettings(settings, m_CMSManager->getDefaultSettings());
|
m_settings->readSettings(settings, m_CMSManager->getDefaultSettings());
|
||||||
m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings());
|
m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings());
|
||||||
|
m_textToSpeech->setSettings(m_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDFViewerMainWindow::readActionSettings()
|
void PDFViewerMainWindow::readActionSettings()
|
||||||
@ -956,6 +960,7 @@ void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_toolManager->setDocument(document);
|
m_toolManager->setDocument(document);
|
||||||
|
m_textToSpeech->setDocument(document);
|
||||||
m_pdfWidget->setDocument(document, m_optionalContentActivity);
|
m_pdfWidget->setDocument(document, m_optionalContentActivity);
|
||||||
m_sidebarWidget->setDocument(document, m_optionalContentActivity);
|
m_sidebarWidget->setDocument(document, m_optionalContentActivity);
|
||||||
m_advancedFindWidget->setDocument(document);
|
m_advancedFindWidget->setDocument(document);
|
||||||
@ -1130,6 +1135,7 @@ void PDFViewerMainWindow::on_actionOptions_triggered()
|
|||||||
m_settings->setColorManagementSystemSettings(dialog.getCMSSettings());
|
m_settings->setColorManagementSystemSettings(dialog.getCMSSettings());
|
||||||
m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings());
|
m_CMSManager->setSettings(m_settings->getColorManagementSystemSettings());
|
||||||
m_recentFileManager->setRecentFilesLimit(dialog.getOtherSettings().maximumRecentFileCount);
|
m_recentFileManager->setRecentFilesLimit(dialog.getOtherSettings().maximumRecentFileCount);
|
||||||
|
m_textToSpeech->setSettings(m_settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "pdfdocumentpropertiesdialog.h"
|
#include "pdfdocumentpropertiesdialog.h"
|
||||||
#include "pdfwidgettool.h"
|
#include "pdfwidgettool.h"
|
||||||
#include "pdfrecentfilemanager.h"
|
#include "pdfrecentfilemanager.h"
|
||||||
|
#include "pdftexttospeech.h"
|
||||||
|
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
@ -177,6 +178,7 @@ private:
|
|||||||
bool m_isChangingProgressStep;
|
bool m_isChangingProgressStep;
|
||||||
|
|
||||||
pdf::PDFToolManager* m_toolManager;
|
pdf::PDFToolManager* m_toolManager;
|
||||||
|
PDFTextToSpeech* m_textToSpeech;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdfviewer
|
} // 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 |
Loading…
x
Reference in New Issue
Block a user