Tool for text selection (first part)

This commit is contained in:
Jakub Melka 2020-01-25 17:36:25 +01:00
parent 7ad4c46124
commit 95f6135482
15 changed files with 911 additions and 14 deletions

View File

@ -227,3 +227,11 @@ qt_libraries.files = $$[QT_INSTALL_BINS]/Qt?Widgets$${SUFFIX}.dll \
$$[QT_INSTALL_BINS]/Qt?Svg$${SUFFIX}.dll
qt_libraries.path = $$DESTDIR/install
INSTALLS += qt_libraries
qt_plugin_platform.files = $$[QT_INSTALL_PLUGINS]/platforms/qwindows$${SUFFIX}.dll
qt_plugin_platform.path = $$DESTDIR/install/platforms
INSTALLS += qt_plugin_platform
qt_plugin_iconengine.files = $$[QT_INSTALL_PLUGINS]/iconengines/qsvgicon$${SUFFIX}.dll
qt_plugin_iconengine.path = $$DESTDIR/install/iconengines
INSTALLS += qt_plugin_iconengine

View File

@ -784,6 +784,31 @@ std::vector<PDFInteger> PDFDrawWidgetProxy::getPagesIntersectingRect(QRect rect)
return pages;
}
PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const
{
// Iterate trough pages, place them and test, if they intersects with rectangle
for (const LayoutItem& item : m_layout.items)
{
// The offsets m_horizontalOffset and m_verticalOffset are offsets to the
// topleft point of the block. But block maybe doesn't start at (0, 0),
// so we must also use translation from the block beginning.
QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top());
if (placedRect.contains(point))
{
if (pagePoint)
{
const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex);
QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect).inverted();
*pagePoint = matrix.map(point);
}
return item.pageIndex;
}
}
return -1;
}
QRect PDFDrawWidgetProxy::getPagesIntersectingRectBoundingBox(QRect rect) const
{
QRect resultRect;

View File

@ -35,7 +35,6 @@ namespace pdf
class PDFProgress;
class PDFWidget;
class IDrawWidget;
class PDFWidgetTool;
class PDFCMSManager;
class PDFTextLayoutGetter;
class PDFAsynchronousPageCompiler;
@ -269,6 +268,13 @@ public:
/// \param rect Rectangle to test
std::vector<PDFInteger> getPagesIntersectingRect(QRect rect) const;
/// Returns page, under which is point. If no page is under the point,
/// then -1 is returned. Point is in widget coordinates. If \p pagePoint
/// is not nullptr, then point in page coordinate space is set here.
/// \param point Point
/// \param pagePoint Point in page coordinate system
PDFInteger getPageUnderPoint(QPoint point, QPointF* pagePoint) const;
/// Returns bounding box of pages, which are intersecting rectangle (even partially)
/// \param rect Rectangle to test
QRect getPagesIntersectingRectBoundingBox(QRect rect) const;

View File

@ -18,6 +18,7 @@
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include "pdfcompiler.h"
#include "pdfwidgettool.h"
#include <QPainter>
#include <QGridLayout>
@ -31,6 +32,7 @@ namespace pdf
PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent) :
QWidget(parent),
m_cmsManager(cmsManager),
m_toolManager(nullptr),
m_drawWidget(nullptr),
m_horizontalScrollBar(nullptr),
m_verticalScrollBar(nullptr),
@ -177,6 +179,7 @@ PDFDrawWidgetBase<BaseWidget>::PDFDrawWidgetBase(PDFWidget* widget, QWidget* par
m_mouseOperation(MouseOperation::None)
{
this->setFocusPolicy(Qt::StrongFocus);
this->setMouseTracking(true);
}
template<typename BaseWidget>
@ -216,10 +219,21 @@ void PDFDrawWidgetBase<BaseWidget>::performMouseOperation(QPoint currentMousePos
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
{
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
event->ignore();
// Try to pass event to tool manager
if (PDFToolManager* toolManager = getPDFWidget()->getToolManager())
{
toolManager->keyPressEvent(this, event);
if (event->isAccepted())
{
updateCursor();
return;
}
}
// Vertical navigation
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
if (verticalScrollbar->isVisible())
{
constexpr std::pair<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation> keyToOperations[] =
@ -241,24 +255,52 @@ void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
}
}
}
updateCursor();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mousePressEvent(QMouseEvent* event)
{
event->ignore();
// Try to pass event to tool manager
if (PDFToolManager* toolManager = getPDFWidget()->getToolManager())
{
toolManager->mousePressEvent(this, event);
if (event->isAccepted())
{
updateCursor();
return;
}
}
if (event->button() == Qt::LeftButton)
{
m_mouseOperation = MouseOperation::Translate;
m_lastMousePosition = event->pos();
setCursor(Qt::ClosedHandCursor);
}
updateCursor();
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
{
event->ignore();
// Try to pass event to tool manager
if (PDFToolManager* toolManager = getPDFWidget()->getToolManager())
{
toolManager->mouseReleaseEvent(this, event);
if (event->isAccepted())
{
updateCursor();
return;
}
}
performMouseOperation(event->pos());
switch (m_mouseOperation)
@ -269,7 +311,6 @@ void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
case MouseOperation::Translate:
{
m_mouseOperation = MouseOperation::None;
unsetCursor();
break;
}
@ -277,19 +318,83 @@ void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
Q_ASSERT(false);
}
updateCursor();
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseMoveEvent(QMouseEvent* event)
{
event->ignore();
// Try to pass event to tool manager
if (PDFToolManager* toolManager = getPDFWidget()->getToolManager())
{
toolManager->mouseMoveEvent(this, event);
if (event->isAccepted())
{
updateCursor();
return;
}
}
performMouseOperation(event->pos());
updateCursor();
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::updateCursor()
{
std::optional<QCursor> cursor;
if (PDFToolManager* toolManager = m_widget->getToolManager())
{
cursor = toolManager->getCursor();
}
if (!cursor)
{
switch (m_mouseOperation)
{
case MouseOperation::None:
cursor = QCursor(Qt::OpenHandCursor);
break;
case MouseOperation::Translate:
cursor = QCursor(Qt::ClosedHandCursor);
break;
default:
Q_ASSERT(false);
break;
}
}
if (cursor)
{
this->setCursor(*cursor);
}
else
{
this->unsetCursor();
}
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::wheelEvent(QWheelEvent* event)
{
event->ignore();
// Try to pass event to tool manager
if (PDFToolManager* toolManager = getPDFWidget()->getToolManager())
{
toolManager->wheelEvent(this, event);
if (event->isAccepted())
{
return;
}
}
Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers();
PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();

View File

@ -29,6 +29,7 @@ namespace pdf
{
class PDFDocument;
class PDFCMSManager;
class PDFToolManager;
class PDFDrawWidget;
class PDFDrawWidgetProxy;
@ -77,6 +78,7 @@ public:
void updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit);
const PDFCMSManager* getCMSManager() const { return m_cmsManager; }
PDFToolManager* getToolManager() const { return m_toolManager; }
IDrawWidget* getDrawWidget() const { return m_drawWidget; }
QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; }
QScrollBar* getVerticalScrollbar() const { return m_verticalScrollBar; }
@ -84,6 +86,8 @@ public:
const PageRenderingErrors* getPageRenderingErrors() const { return &m_pageRenderingErrors; }
int getPageRenderingErrorCount() const;
void setToolManager(PDFToolManager* toolManager) { m_toolManager = toolManager; }
signals:
void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount);
@ -95,6 +99,7 @@ private:
IDrawWidget* createDrawWidget(RendererEngine rendererEngine, int samplesCount);
const PDFCMSManager* m_cmsManager;
PDFToolManager* m_toolManager;
IDrawWidget* m_drawWidget;
QScrollBar* m_horizontalScrollBar;
QScrollBar* m_verticalScrollBar;
@ -125,6 +130,8 @@ protected:
PDFWidget* getPDFWidget() const { return m_widget; }
private:
void updateCursor();
enum class MouseOperation
{
None,

View File

@ -349,6 +349,19 @@ qint64 PDFTextLayout::getMemoryConsumptionEstimate() const
return estimate;
}
bool PDFTextLayout::isHoveringOverTextBlock(const QPointF& point) const
{
for (const PDFTextBlock& block : m_blocks)
{
if (block.getBoundingBox().contains(point))
{
return true;
}
}
return false;
}
QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout)
{
stream >> layout.m_characters;

View File

@ -200,6 +200,9 @@ struct PDFTextSelectionColoredItem
}
bool operator==(const PDFTextSelectionColoredItem&) const = default;
bool operator!=(const PDFTextSelectionColoredItem&) const = default;
inline bool operator<(const PDFTextSelectionColoredItem& other) const { return std::tie(start, end) < std::tie(other.start, other.end); }
PDFCharacterPointer start;
@ -217,6 +220,9 @@ public:
using iterator = PDFTextSelectionColoredItems::const_iterator;
bool operator==(const PDFTextSelection&) const = default;
bool operator!=(const PDFTextSelection&) const = default;
/// Adds text selection items to selection
/// \param items Items
/// \param color Color for items (must include alpha channel)
@ -232,6 +238,9 @@ public:
/// Returns iterator to end of page range
iterator end(PDFInteger pageIndex) const;
/// Returns true, if text selection is empty
bool isEmpty() const { return m_items.empty(); }
private:
PDFTextSelectionColoredItems m_items;
};
@ -327,6 +336,9 @@ public:
/// Returns recognized text blocks
const PDFTextBlocks& getTextBlocks() const { return m_blocks; }
/// Returns true, if given point is pointing to some text block
bool isHoveringOverTextBlock(const QPointF& point) const;
friend QDataStream& operator<<(QDataStream& stream, const PDFTextLayout& layout);
friend QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout);

View File

@ -25,6 +25,9 @@
#include <QLineEdit>
#include <QGridLayout>
#include <QPushButton>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
namespace pdf
{
@ -33,6 +36,17 @@ PDFWidgetTool::PDFWidgetTool(PDFDrawWidgetProxy* proxy, QObject* parent) :
BaseClass(parent),
m_active(false),
m_document(nullptr),
m_action(nullptr),
m_proxy(proxy)
{
}
PDFWidgetTool::PDFWidgetTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) :
BaseClass(parent),
m_active(false),
m_document(nullptr),
m_action(action),
m_proxy(proxy)
{
@ -62,6 +76,7 @@ void PDFWidgetTool::setDocument(const PDFDocument* document)
// We must turn off the tool, if we are changing the document
setActive(false);
m_document = document;
updateActions();
}
}
@ -81,17 +96,57 @@ void PDFWidgetTool::setActive(bool active)
}
setActiveImpl(active);
updateActions();
m_proxy->repaintNeeded();
emit toolActivityChanged(active);
}
}
void PDFWidgetTool::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFWidgetTool::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFWidgetTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFWidgetTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFWidgetTool::wheelEvent(QWidget* widget, QWheelEvent* event)
{
Q_UNUSED(widget);
Q_UNUSED(event);
}
void PDFWidgetTool::setActiveImpl(bool active)
{
Q_UNUSED(active);
}
void PDFWidgetTool::updateActions()
{
if (m_action)
{
m_action->setChecked(isActive());
m_action->setEnabled(m_document);
}
}
PDFFindTextTool::PDFFindTextTool(PDFDrawWidgetProxy* proxy, QAction* prevAction, QAction* nextAction, QObject* parent, QWidget* parentDialog) :
BaseClass(proxy, parent),
m_prevAction(prevAction),
@ -143,7 +198,7 @@ void PDFFindTextTool::setActiveImpl(bool active)
getProxy()->getTextLayoutCompiler()->makeTextLayout();
// Create dialog
m_dialog = new QDialog(m_parentDialog, Qt::Tool);
m_dialog = new QDialog(m_parentDialog, Qt::Popup | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
m_dialog->setWindowTitle(tr("Find"));
QGridLayout* layout = new QGridLayout(m_dialog);
@ -164,6 +219,9 @@ void PDFFindTextTool::setActiveImpl(bool active)
m_previousButton->setDefault(false);
m_nextButton->setDefault(false);
m_previousButton->setShortcut(m_prevAction->shortcut());
m_nextButton->setShortcut(m_nextAction->shortcut());
connect(m_previousButton, &QPushButton::clicked, m_prevAction, &QAction::trigger);
connect(m_nextButton, &QPushButton::clicked, m_nextAction, &QAction::trigger);
connect(m_findTextEdit, &QLineEdit::editingFinished, this, &PDFFindTextTool::onSearchText);
@ -184,6 +242,8 @@ void PDFFindTextTool::setActiveImpl(bool active)
m_dialog->show();
m_dialog->move(topRightParent - QPoint(m_dialog->width() * 1.1, 0));
m_dialog->setFocus();
m_findTextEdit->setFocus();
connect(m_dialog, &QDialog::rejected, this, [this] { setActive(false); });
}
else
@ -329,6 +389,8 @@ void PDFFindTextTool::performSearch()
void PDFFindTextTool::updateActions()
{
BaseClass::updateActions();
const bool isActive = this->isActive();
const bool hasResults = !m_findResults.empty();
const bool enablePrevious = isActive && hasResults;
@ -384,15 +446,161 @@ PDFTextSelection PDFFindTextTool::getTextSelectionImpl() const
return result;
}
PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, QAction* findPreviousAction, QAction* findNextAction, QObject* parent, QWidget* parentDialog) :
PDFSelectTextTool::PDFSelectTextTool(PDFDrawWidgetProxy* proxy, QAction* action, QAction* selectAllAction, QAction* deselectAction, QObject* parent) :
BaseClass(proxy, action, parent),
m_selectAllAction(selectAllAction),
m_deselectAction(deselectAction),
m_isCursorOverText(false)
{
updateActions();
}
void PDFSelectTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
if (event->button() == Qt::LeftButton)
{
QPointF pagePoint;
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
if (pageIndex != -1)
{
m_selectionInfo.pageIndex = pageIndex;
m_selectionInfo.selectionStartPoint = pagePoint;
event->accept();
}
else
{
m_selectionInfo = SelectionInfo();
}
setSelection(pdf::PDFTextSelection());
updateCursor();
}
}
void PDFSelectTextTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
if (event->button() == Qt::LeftButton)
{
if (m_selectionInfo.pageIndex != -1)
{
QPointF pagePoint;
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
if (m_selectionInfo.pageIndex == pageIndex)
{
// Jakub Melka: handle the selection
}
else
{
setSelection(pdf::PDFTextSelection());
}
m_selectionInfo = SelectionInfo();
event->accept();
updateCursor();
}
}
}
void PDFSelectTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
Q_UNUSED(widget);
QPointF pagePoint;
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
PDFTextLayout textLayout = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex);
m_isCursorOverText = textLayout.isHoveringOverTextBlock(pagePoint);
if (event->button() == Qt::LeftButton)
{
if (m_selectionInfo.pageIndex != -1)
{
if (m_selectionInfo.pageIndex == pageIndex)
{
// Jakub Melka: handle the selection
}
else
{
setSelection(pdf::PDFTextSelection());
}
event->accept();
}
}
updateCursor();
}
void PDFSelectTextTool::setActiveImpl(bool active)
{
if (active)
{
pdf::PDFAsynchronousTextLayoutCompiler* compiler = getProxy()->getTextLayoutCompiler();
if (!compiler->isTextLayoutReady())
{
compiler->makeTextLayout();
}
}
else
{
// Just clear the text selection
setSelection(PDFTextSelection());
}
}
void PDFSelectTextTool::updateActions()
{
BaseClass::updateActions();
m_selectAllAction->setEnabled(isActive());
m_deselectAction->setEnabled(isActive() && !m_textSelection.isEmpty());
}
void PDFSelectTextTool::updateCursor()
{
if (isActive())
{
if (m_isCursorOverText)
{
setCursor(QCursor(Qt::IBeamCursor));
}
else
{
setCursor(QCursor(Qt::ArrowCursor));
}
}
}
void PDFSelectTextTool::setSelection(PDFTextSelection&& textSelection)
{
if (m_textSelection != textSelection)
{
m_textSelection = qMove(textSelection);
getProxy()->repaintNeeded();
updateActions();
}
}
PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObject* parent, QWidget* parentDialog) :
BaseClass(parent),
m_predefinedTools()
{
m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, findPreviousAction, findNextAction, this, parentDialog);
m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, actions.findPrevAction, actions.findNextAction, this, parentDialog);
m_predefinedTools[SelectTextTool] = new PDFSelectTextTool(proxy, actions.selectTextToolAction, actions.selectAllAction, actions.deselectAction, this);
for (PDFWidgetTool* tool : m_predefinedTools)
{
m_tools.insert(tool);
if (QAction* action = tool->getAction())
{
m_actionsToTools[action] = tool;
connect(action, &QAction::triggered, this, &PDFToolManager::onToolActionTriggered);
}
}
}
@ -404,6 +612,22 @@ void PDFToolManager::setDocument(const PDFDocument* document)
}
}
void PDFToolManager::setActiveTool(PDFWidgetTool* tool)
{
PDFWidgetTool* activeTool = getActiveTool();
if (activeTool && activeTool != tool)
{
activeTool->setActive(false);
}
Q_ASSERT(!getActiveTool());
if (tool)
{
tool->setActive(true);
}
}
PDFWidgetTool* PDFToolManager::getActiveTool() const
{
for (PDFWidgetTool* tool : m_tools)
@ -422,4 +646,88 @@ PDFFindTextTool* PDFToolManager::getFindTextTool() const
return qobject_cast<PDFFindTextTool*>(m_predefinedTools[FindTextTool]);
}
void PDFToolManager::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
event->ignore();
// Escape key cancels current tool
PDFWidgetTool* activeTool = getActiveTool();
if (event->key() == Qt::Key_Escape && activeTool)
{
activeTool->setActive(false);
event->accept();
return;
}
if (activeTool)
{
activeTool->keyPressEvent(widget, event);
}
}
void PDFToolManager::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
event->ignore();
if (PDFWidgetTool* activeTool = getActiveTool())
{
activeTool->mousePressEvent(widget, event);
}
}
void PDFToolManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
{
event->ignore();
if (PDFWidgetTool* activeTool = getActiveTool())
{
activeTool->mouseReleaseEvent(widget, event);
}
}
void PDFToolManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
event->ignore();
if (PDFWidgetTool* activeTool = getActiveTool())
{
activeTool->mouseMoveEvent(widget, event);
}
}
void PDFToolManager::wheelEvent(QWidget* widget, QWheelEvent* event)
{
event->ignore();
if (PDFWidgetTool* activeTool = getActiveTool())
{
activeTool->wheelEvent(widget, event);
}
}
const std::optional<QCursor>& PDFToolManager::getCursor() const
{
if (PDFWidgetTool* tool = getActiveTool())
{
return tool->getCursor();
}
static const std::optional<QCursor> dummy;
return dummy;
}
void PDFToolManager::onToolActionTriggered(bool checked)
{
PDFWidgetTool* tool = m_actionsToTools.at(qobject_cast<QAction*>(sender()));
if (checked)
{
setActiveTool(tool);
}
else
{
tool->setActive(false);
}
}
} // namespace pdf

View File

@ -22,6 +22,7 @@
#include "pdftextlayout.h"
#include <QDialog>
#include <QCursor>
class QCheckBox;
@ -40,6 +41,7 @@ private:
public:
explicit PDFWidgetTool(PDFDrawWidgetProxy* proxy, QObject* parent);
explicit PDFWidgetTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent);
virtual ~PDFWidgetTool();
virtual void drawPage(QPainter* painter,
@ -61,24 +63,62 @@ public:
/// Returns true, if tool is active
bool isActive() const { return m_active; }
/// Returns action for activating/deactivating this tool
QAction* getAction() const { return m_action; }
/// Handles key press event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event);
/// Handles mouse press event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse release event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse move event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse wheel event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
virtual void wheelEvent(QWidget* widget, QWheelEvent* event);
/// Returns actual cursor defined by the tool. Cursor can be undefined,
/// in this case, optional will be set to nullopt.
const std::optional<QCursor>& getCursor() const { return m_cursor; }
signals:
void toolActivityChanged(bool active);
protected:
virtual void setActiveImpl(bool active);
virtual void updateActions();
PDFDrawWidgetProxy* getProxy() const { return m_proxy; }
inline void setCursor(QCursor cursor) { m_cursor = qMove(cursor); }
inline void unsetCursor() { m_cursor = std::nullopt; }
private:
bool m_active;
const PDFDocument* m_document;
QAction* m_action;
PDFDrawWidgetProxy* m_proxy;
std::vector<PDFWidgetTool*> m_toolStack;
std::optional<QCursor> m_cursor;
};
/// Simple tool for find text in PDF document. It is much simpler than advanced
/// search and can't search using regular expressions.
class PDFFORQTLIBSHARED_EXPORT PDFFindTextTool : public PDFWidgetTool
class PDFFindTextTool : public PDFWidgetTool
{
Q_OBJECT
@ -102,6 +142,7 @@ public:
protected:
virtual void setActiveImpl(bool active) override;
virtual void updateActions() override;
private:
void onSearchText();
@ -109,7 +150,6 @@ private:
void onActionNext();
void performSearch();
void updateActions();
void updateResultsUI();
void updateTitle();
void clearResults();
@ -142,6 +182,45 @@ private:
mutable pdf::PDFCachedItem<pdf::PDFTextSelection> m_textSelection;
};
/// Tool for selection of text in document
class PDFSelectTextTool : public PDFWidgetTool
{
Q_OBJECT
private:
using BaseClass = PDFWidgetTool;
public:
/// Construct new text selection tool
/// \param proxy Draw widget proxy
/// \param parent Parent object
explicit PDFSelectTextTool(PDFDrawWidgetProxy* proxy, QAction* action, QAction* selectAllAction,QAction* deselectAction, QObject* parent);
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override;
virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override;
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override;
protected:
virtual void setActiveImpl(bool active) override;
virtual void updateActions() override;
private:
void updateCursor();
void setSelection(pdf::PDFTextSelection&& textSelection);
struct SelectionInfo
{
PDFInteger pageIndex = -1;
QPointF selectionStartPoint;
};
QAction* m_selectAllAction;
QAction* m_deselectAction;
pdf::PDFTextSelection m_textSelection;
SelectionInfo m_selectionInfo;
bool m_isCursorOverText;
};
/// Manager used for managing tools, their activity, availability
/// and other settings. It also defines a predefined set of tools,
/// available for various purposes (text searching, magnifier tool etc.)
@ -153,13 +232,21 @@ private:
using BaseClass = QObject;
public:
struct Actions
{
QAction* findPrevAction = nullptr; ///< Action for navigating to previous result
QAction* findNextAction = nullptr; ///< Action for navigating to next result
QAction* selectTextToolAction = nullptr;
QAction* selectAllAction = nullptr;
QAction* deselectAction = nullptr;
};
/// Construct new text search tool
/// \param proxy Draw widget proxy
/// \param prevAction Action for navigating to previous result
/// \param nextAction Action for navigating to next result
/// \param actions Actions
/// \param parent Parent object
/// \param parentDialog Paret dialog for tool dialog
explicit PDFToolManager(PDFDrawWidgetProxy* proxy, QAction* findPreviousAction, QAction* findNextAction, QObject* parent, QWidget* parentDialog);
explicit PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObject* parent, QWidget* parentDialog);
/// Sets document
/// \param document Document
@ -168,9 +255,13 @@ public:
enum PredefinedTools
{
FindTextTool,
SelectTextTool,
ToolEnd
};
/// Sets active tool
void setActiveTool(PDFWidgetTool* tool);
/// Returns first active tool from tool set. If no tool is active,
/// then nullptr is returned.
PDFWidgetTool* getActiveTool() const;
@ -178,9 +269,41 @@ public:
/// Returns find text tool
PDFFindTextTool* getFindTextTool() const;
/// Handles key press event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
void keyPressEvent(QWidget* widget, QKeyEvent* event);
/// Handles mouse press event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
void mousePressEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse release event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
void mouseReleaseEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse move event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
void mouseMoveEvent(QWidget* widget, QMouseEvent* event);
/// Handles mouse wheel event from widget, over which tool operates
/// \param widget Widget, over which tool operates
/// \param event Event
void wheelEvent(QWidget* widget, QWheelEvent* event);
/// Returns actual cursor defined by the tool. Cursor can be undefined,
/// in this case, optional will be set to nullopt.
const std::optional<QCursor>& getCursor() const;
private:
void onToolActionTriggered(bool checked);
std::set<PDFWidgetTool*> m_tools;
std::array<PDFWidgetTool*, ToolEnd> m_predefinedTools;
std::map<QAction*, PDFWidgetTool*> m_actionsToTools;
};
} // namespace pdf

View File

@ -31,5 +31,6 @@
<file>resources/find-advanced.svg</file>
<file>resources/find-next.svg</file>
<file>resources/find-previous.svg</file>
<file>resources/select-text.svg</file>
</qresource>
</RCC>

View File

@ -35,6 +35,7 @@
#include "pdfutils.h"
#include "pdfsendmail.h"
#include "pdfexecutionpolicy.h"
#include "pdfwidgetutils.h"
#include <QSettings>
#include <QFileDialog>
@ -85,6 +86,10 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
{
ui->setupUi(this);
// Initialize toolbar icon size
QSize iconSize = PDFWidgetUtils::scaleDPI(this, QSize(24, 24));
ui->mainToolBar->setIconSize(iconSize);
// Initialize task bar progress
m_progressTaskbarIndicator = m_taskbarButton->progress();
@ -97,6 +102,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
ui->actionFind->setShortcut(QKeySequence::Find);
ui->actionFindPrevious->setShortcut(QKeySequence::FindPrevious);
ui->actionFindNext->setShortcut(QKeySequence::FindNext);
ui->actionSelectTextAll->setShortcut(QKeySequence::SelectAll);
ui->actionDeselectText->setShortcut(QKeySequence::Deselect);
connect(ui->actionOpen, &QAction::triggered, this, &PDFViewerMainWindow::onActionOpenTriggered);
connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered);
@ -160,6 +167,10 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
ui->mainToolBar->addAction(ui->actionFitPage);
ui->mainToolBar->addAction(ui->actionFitWidth);
ui->mainToolBar->addAction(ui->actionFitHeight);
ui->mainToolBar->addSeparator();
// Tools
ui->mainToolBar->addAction(ui->actionSelectText);
connect(ui->actionZoom_In, &QAction::triggered, this, [this] { m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::ZoomIn); });
connect(ui->actionZoom_Out, &QAction::triggered, this, [this] { m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::ZoomOut); });
@ -212,7 +223,14 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
m_sidebarDockWidget->toggleViewAction()->setObjectName("actionSidebar");
// Initialize tools
m_toolManager = new pdf::PDFToolManager(m_pdfWidget->getDrawWidgetProxy(), ui->actionFindPrevious, ui->actionFindNext, this, this);
pdf::PDFToolManager::Actions actions;
actions.findPrevAction = ui->actionFindPrevious;
actions.findNextAction = ui->actionFindNext;
actions.selectTextToolAction = ui->actionSelectText;
actions.selectAllAction = ui->actionSelectTextAll;
actions.deselectAction = ui->actionDeselectText;
m_toolManager = new pdf::PDFToolManager(m_pdfWidget->getDrawWidgetProxy(), actions, this, this);
m_pdfWidget->setToolManager(m_toolManager);
connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFViewerMainWindow::onDrawSpaceChanged);
connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::pageLayoutChanged, this, &PDFViewerMainWindow::onPageLayoutChanged);
@ -223,7 +241,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
connect(m_progress, &pdf::PDFProgress::progressFinished, this, &PDFViewerMainWindow::onProgressFinished);
connect(&m_futureWatcher, &QFutureWatcher<AsyncReadingResult>::finished, this, &PDFViewerMainWindow::onDocumentReadingFinished);
connect(this, &PDFViewerMainWindow::queryPasswordRequest, this, &PDFViewerMainWindow::onQueryPasswordRequest, Qt::BlockingQueuedConnection);
connect(ui->actionFind, &QAction::triggered, this, [this] { m_toolManager->getFindTextTool()->setActive(true); });
connect(ui->actionFind, &QAction::triggered, this, [this] { m_toolManager->setActiveTool(m_toolManager->getFindTextTool()); });
readActionSettings();
updatePageLayoutActions();

View File

@ -103,6 +103,11 @@
<addaction name="actionFind"/>
<addaction name="actionFindPrevious"/>
<addaction name="actionFindNext"/>
<addaction name="separator"/>
<addaction name="actionSelectText"/>
<addaction name="actionSelectTextAll"/>
<addaction name="actionDeselectText"/>
<addaction name="separator"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
@ -376,6 +381,28 @@
<string>Find Next</string>
</property>
</action>
<action name="actionSelectText">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="pdfforqtviewer.qrc">
<normaloff>:/resources/select-text.svg</normaloff>:/resources/select-text.svg</iconset>
</property>
<property name="text">
<string>Select text</string>
</property>
</action>
<action name="actionSelectTextAll">
<property name="text">
<string>Select All</string>
</property>
</action>
<action name="actionDeselectText">
<property name="text">
<string>Deselect</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

View File

@ -63,4 +63,17 @@ void PDFWidgetUtils::scaleWidget(QWidget* widget, QSize unscaledSize)
widget->resize(width, height);
}
QSize PDFWidgetUtils::scaleDPI(QWidget* widget, QSize unscaledSize)
{
const double logicalDPI_x = widget->logicalDpiX();
const double logicalDPI_y = widget->logicalDpiY();
const double defaultDPI_x = qt_default_dpi_x();
const double defaultDPI_y = qt_default_dpi_y();
const int width = (logicalDPI_x / defaultDPI_x) * unscaledSize.width();
const int height = (logicalDPI_y / defaultDPI_y) * unscaledSize.height();
return QSize(width, height);
}
} // namespace pdfviewer

View File

@ -40,6 +40,11 @@ public:
/// \param widget Widget to be scaled
/// \param unscaledSize Unscaled size of the widget
static void scaleWidget(QWidget* widget, QSize unscaledSize);
/// Scales size based on DPI
/// \param widget Widget, from which we get DPI
/// \param unscaledSize Unscaled size
static QSize scaleDPI(QWidget* widget, QSize unscaledSize);
};
} // namespace pdfviewer

View File

@ -0,0 +1,226 @@
<?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="select-text.svg">
<defs
id="defs5285">
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1935"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1933"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Club"
orient="auto"
refY="0.0"
refX="0.0"
id="Club"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1637"
d="M -1.5971367,-7.0977635 C -3.4863874,-7.0977635 -5.0235187,-5.5606321 -5.0235187,-3.6713813 C -5.0235187,-3.0147015 -4.7851656,-2.4444556 -4.4641095,-1.9232271 C -4.5028609,-1.8911157 -4.5437814,-1.8647646 -4.5806531,-1.8299921 C -5.2030765,-2.6849849 -6.1700514,-3.2751330 -7.3077730,-3.2751330 C -9.1970245,-3.2751331 -10.734155,-1.7380016 -10.734155,0.15124914 C -10.734155,2.0404999 -9.1970245,3.5776313 -7.3077730,3.5776313 C -6.3143268,3.5776313 -5.4391540,3.1355702 -4.8137404,2.4588126 C -4.9384274,2.8137041 -5.0235187,3.1803000 -5.0235187,3.5776313 C -5.0235187,5.4668819 -3.4863874,7.0040135 -1.5971367,7.0040135 C 0.29211394,7.0040135 1.8292454,5.4668819 1.8292454,3.5776313 C 1.8292454,2.7842354 1.5136868,2.0838028 1.0600576,1.5031550 C 2.4152718,1.7663868 3.7718375,2.2973711 4.7661444,3.8340272 C 4.0279463,3.0958289 3.5540908,1.7534117 3.5540908,-0.058529361 L 2.9247554,-0.10514681 L 3.5074733,-0.12845553 C 3.5074733,-1.9403966 3.9580199,-3.2828138 4.6962183,-4.0210121 C 3.7371277,-2.5387813 2.4390549,-1.9946496 1.1299838,-1.7134486 C 1.5341802,-2.2753578 1.8292454,-2.9268556 1.8292454,-3.6713813 C 1.8292454,-5.5606319 0.29211394,-7.0977635 -1.5971367,-7.0977635 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:0.74587913pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.6)" />
</marker>
<marker
inkscape:stockid="Torso"
orient="auto"
refY="0.0"
refX="0.0"
id="Torso"
style="overflow:visible"
inkscape:isstock="true">
<g
id="g1634"
transform="scale(0.7)"
style="stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1">
<path
id="path1620"
d="M -4.7792281,-3.2395420 C -2.4288541,-2.8736027 0.52103922,-1.3019943 0.25792722,0.38794346 C -0.0051877922,2.0778819 -2.2126741,2.6176539 -4.5630471,2.2517169 C -6.9134221,1.8857769 -8.5210350,0.75201414 -8.2579220,-0.93792336 C -7.9948090,-2.6278615 -7.1296041,-3.6054813 -4.7792281,-3.2395420 z "
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.25;stroke-opacity:1" />
<path
id="path1622"
d="M 4.4598789,0.088665736 C -2.5564571,-4.3783320 5.2248769,-3.9061806 -0.84829578,-8.7197331"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
<path
id="path1624"
d="M 4.9298719,0.057520736 C -1.3872731,1.7494689 1.8027579,5.4782079 -4.9448731,7.5462725"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
<rect
id="rect1626"
transform="matrix(0.527536,-0.849533,0.887668,0.460484,0,0)"
y="-1.7408575"
x="-10.391706"
height="2.7608147"
width="2.6366582"
style="fill-rule:evenodd;stroke-width:1pt;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" />
<rect
id="rect1628"
transform="matrix(0.671205,-0.741272,0.790802,0.612072,0,0)"
y="-7.9629307"
x="4.9587269"
height="2.8614161"
width="2.7327356"
style="fill-rule:evenodd;stroke-width:1pt;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" />
<path
id="path1630"
transform="matrix(0,-1.109517,1.109517,0,25.96648,19.71619)"
d="M 16.779951 -28.685045 A 0.60731727 0.60731727 0 1 0 15.565317,-28.685045 A 0.60731727 0.60731727 0 1 0 16.779951 -28.685045 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
<path
id="path1632"
transform="matrix(0,-1.109517,1.109517,0,26.82450,16.99126)"
d="M 16.779951 -28.685045 A 0.60731727 0.60731727 0 1 0 15.565317,-28.685045 A 0.60731727 0.60731727 0 1 0 16.779951 -28.685045 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
</g>
</marker>
<marker
inkscape:stockid="Arrow2Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow2Lstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1426"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(1.1) translate(1,0)" />
</marker>
<marker
inkscape:stockid="TriangleInL"
orient="auto"
refY="0.0"
refX="0.0"
id="TriangleInL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1541"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(-0.8)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Lstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1408"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="-0.15372919"
inkscape:cy="4.1302353"
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)">
<text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.28888893px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Italic';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="2.0528727"
y="290.79916"
id="text1404"><tspan
sodipodi:role="line"
id="tspan1402"
x="2.0528727"
y="290.79916"
style="font-size:11.28888893px;stroke-width:0.26458332">abc</tspan></text>
<g
id="g2058">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 12.566406,271.99023 -0.533203,0.59571 13.763672,12.29492 0.533203,-0.5957 z"
id="path1406"
inkscape:connector-curvature="0" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.64000006pt;stroke-opacity:1"
d="m 18.266245,277.61759 4.518301,-0.25488 -10.484776,-5.0744 6.221355,9.84758 z"
id="path2064"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB