mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Merge remote-tracking branch 'remotes/origin/issue21'
This commit is contained in:
@@ -351,6 +351,7 @@ public:
|
|||||||
PDFWidget* getWidget() const { return m_widget; }
|
PDFWidget* getWidget() const { return m_widget; }
|
||||||
bool isUsingOpenGL() const { return m_useOpenGL; }
|
bool isUsingOpenGL() const { return m_useOpenGL; }
|
||||||
const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; }
|
const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; }
|
||||||
|
PageRotation getPageRotation() const { return m_controller->getPageRotation(); }
|
||||||
|
|
||||||
void setFeatures(PDFRenderer::Features features);
|
void setFeatures(PDFRenderer::Features features);
|
||||||
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
||||||
|
@@ -469,6 +469,70 @@ PDFTextSelection PDFTextLayout::createTextSelection(PDFInteger pageIndex, const
|
|||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFTextSelection PDFTextLayout::selectBlock(const size_t blockIndex, PDFInteger pageIndex, QColor color) const
|
||||||
|
{
|
||||||
|
PDFTextSelection selection;
|
||||||
|
if (blockIndex >= m_blocks.size())
|
||||||
|
{
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFTextBlock& textBlock = m_blocks[blockIndex];
|
||||||
|
if (textBlock.getLines().empty())
|
||||||
|
{
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFCharacterPointer ptrA;
|
||||||
|
PDFCharacterPointer ptrB;
|
||||||
|
|
||||||
|
ptrA.pageIndex = pageIndex;
|
||||||
|
ptrA.blockIndex = blockIndex;
|
||||||
|
ptrA.lineIndex = 0;
|
||||||
|
ptrA.characterIndex = 0;
|
||||||
|
|
||||||
|
ptrB.pageIndex = pageIndex;
|
||||||
|
ptrB.blockIndex = blockIndex;
|
||||||
|
ptrB.lineIndex = textBlock.getLines().size() - 1;
|
||||||
|
ptrB.characterIndex = textBlock.getLines().back().getCharacters().size() - 1;
|
||||||
|
|
||||||
|
selection.addItems({ PDFTextSelectionItem(ptrA, ptrB) }, color);
|
||||||
|
selection.build();
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFTextSelection PDFTextLayout::selectLineInBlock(const size_t blockIndex, const size_t lineIndex, PDFInteger pageIndex, QColor color) const
|
||||||
|
{
|
||||||
|
PDFTextSelection selection;
|
||||||
|
if (blockIndex >= m_blocks.size())
|
||||||
|
{
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFTextBlock& textBlock = m_blocks[blockIndex];
|
||||||
|
if (lineIndex >= textBlock.getLines().size())
|
||||||
|
{
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFCharacterPointer ptrA;
|
||||||
|
PDFCharacterPointer ptrB;
|
||||||
|
|
||||||
|
ptrA.pageIndex = pageIndex;
|
||||||
|
ptrA.blockIndex = blockIndex;
|
||||||
|
ptrA.lineIndex = lineIndex;
|
||||||
|
ptrA.characterIndex = 0;
|
||||||
|
|
||||||
|
ptrB.pageIndex = pageIndex;
|
||||||
|
ptrB.blockIndex = blockIndex;
|
||||||
|
ptrB.lineIndex = lineIndex;
|
||||||
|
ptrB.characterIndex = textBlock.getLines()[lineIndex].getCharacters().size() - 1;
|
||||||
|
|
||||||
|
selection.addItems({ PDFTextSelectionItem(ptrA, ptrB) }, color);
|
||||||
|
selection.build();
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
QString PDFTextLayout::getTextFromSelection(PDFTextSelection::iterator itBegin, PDFTextSelection::iterator itEnd, PDFInteger pageIndex) const
|
QString PDFTextLayout::getTextFromSelection(PDFTextSelection::iterator itBegin, PDFTextSelection::iterator itEnd, PDFInteger pageIndex) const
|
||||||
{
|
{
|
||||||
QStringList text;
|
QStringList text;
|
||||||
|
@@ -381,6 +381,19 @@ public:
|
|||||||
/// \param pageIndex Index of the page
|
/// \param pageIndex Index of the page
|
||||||
QString getTextFromSelection(const PDFTextSelection& selection, PDFInteger pageIndex) const;
|
QString getTextFromSelection(const PDFTextSelection& selection, PDFInteger pageIndex) const;
|
||||||
|
|
||||||
|
/// Creates text selection for whole block
|
||||||
|
/// \param blockIndex Text block index
|
||||||
|
/// \param pageIndex pageIndex
|
||||||
|
/// \param color Selection color
|
||||||
|
PDFTextSelection selectBlock(const size_t blockIndex, PDFInteger pageIndex, QColor color) const;
|
||||||
|
|
||||||
|
/// Creates text selection for signle line of text block
|
||||||
|
/// \param blockIndex Text block index
|
||||||
|
/// \param lineIndex Line index
|
||||||
|
/// \param pageIndex pageIndex
|
||||||
|
/// \param color Selection color
|
||||||
|
PDFTextSelection selectLineInBlock(const size_t blockIndex, const size_t lineIndex, PDFInteger pageIndex, QColor color) const;
|
||||||
|
|
||||||
friend QDataStream& operator<<(QDataStream& stream, const PDFTextLayout& layout);
|
friend QDataStream& operator<<(QDataStream& stream, const PDFTextLayout& layout);
|
||||||
friend QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout);
|
friend QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout);
|
||||||
|
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
#include "pdfdrawwidget.h"
|
#include "pdfdrawwidget.h"
|
||||||
#include "pdfcompiler.h"
|
#include "pdfcompiler.h"
|
||||||
#include "pdfwidgetutils.h"
|
#include "pdfwidgetutils.h"
|
||||||
|
#include "pdfpainterutils.h"
|
||||||
#include "pdfdbgheap.h"
|
#include "pdfdbgheap.h"
|
||||||
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@@ -745,6 +746,7 @@ PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObje
|
|||||||
m_predefinedTools[PickRectangleTool] = pickTool;
|
m_predefinedTools[PickRectangleTool] = pickTool;
|
||||||
m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, actions.findPrevAction, actions.findNextAction, this, parentDialog);
|
m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, actions.findPrevAction, actions.findNextAction, this, parentDialog);
|
||||||
m_predefinedTools[SelectTextTool] = new PDFSelectTextTool(proxy, actions.selectTextToolAction, actions.copyTextAction, actions.selectAllAction, actions.deselectAction, this);
|
m_predefinedTools[SelectTextTool] = new PDFSelectTextTool(proxy, actions.selectTextToolAction, actions.copyTextAction, actions.selectAllAction, actions.deselectAction, this);
|
||||||
|
m_predefinedTools[SelectTableTool] = new PDFSelectTableTool(proxy, actions.selectTableToolAction, this);
|
||||||
m_predefinedTools[MagnifierTool] = new PDFMagnifierTool(proxy, actions.magnifierAction, this);
|
m_predefinedTools[MagnifierTool] = new PDFMagnifierTool(proxy, actions.magnifierAction, this);
|
||||||
m_predefinedTools[ScreenshotTool] = new PDFScreenshotTool(proxy, actions.screenshotToolAction, this);
|
m_predefinedTools[ScreenshotTool] = new PDFScreenshotTool(proxy, actions.screenshotToolAction, this);
|
||||||
m_predefinedTools[ExtractImageTool] = new PDFExtractImageTool(proxy, actions.extractImageAction, this);
|
m_predefinedTools[ExtractImageTool] = new PDFExtractImageTool(proxy, actions.extractImageAction, this);
|
||||||
@@ -1367,4 +1369,519 @@ void PDFExtractImageTool::onImagePicked(const QImage& image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFSelectTableTool::PDFSelectTableTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) :
|
||||||
|
BaseClass(proxy, action, parent),
|
||||||
|
m_pickTool(nullptr),
|
||||||
|
m_pageIndex(-1),
|
||||||
|
m_isTransposed(false)
|
||||||
|
{
|
||||||
|
m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this);
|
||||||
|
connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFSelectTableTool::onRectanglePicked);
|
||||||
|
|
||||||
|
setCursor(Qt::CrossCursor);
|
||||||
|
updateActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::drawPage(QPainter* painter,
|
||||||
|
PDFInteger pageIndex,
|
||||||
|
const PDFPrecompiledPage* compiledPage,
|
||||||
|
PDFTextLayoutGetter& layoutGetter,
|
||||||
|
const QMatrix& pagePointToDevicePointMatrix,
|
||||||
|
QList<PDFRenderError>& errors) const
|
||||||
|
{
|
||||||
|
BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
|
||||||
|
|
||||||
|
if (isTablePicked() && pageIndex == m_pageIndex)
|
||||||
|
{
|
||||||
|
PDFPainterStateGuard guard(painter);
|
||||||
|
QColor color = QColor::fromRgbF(0.0, 0.0, 0.5, 0.2);
|
||||||
|
QRectF rectangle = pagePointToDevicePointMatrix.mapRect(m_pickedRectangle);
|
||||||
|
|
||||||
|
const PDFReal lineWidth = PDFWidgetUtils::scaleDPI_x(getProxy()->getWidget(), 2.0);
|
||||||
|
QPen pen(Qt::SolidLine);
|
||||||
|
pen.setWidthF(lineWidth);
|
||||||
|
|
||||||
|
painter->setPen(std::move(pen));
|
||||||
|
painter->setBrush(QBrush(color));
|
||||||
|
painter->drawRect(rectangle);
|
||||||
|
|
||||||
|
for (const PDFReal columnPosition : m_horizontalBreaks)
|
||||||
|
{
|
||||||
|
QPointF startPoint(columnPosition, m_pickedRectangle.top());
|
||||||
|
QPointF endPoint(columnPosition, m_pickedRectangle.bottom());
|
||||||
|
|
||||||
|
painter->drawLine(pagePointToDevicePointMatrix.map(startPoint), pagePointToDevicePointMatrix.map(endPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const PDFReal rowPosition : m_verticalBreaks)
|
||||||
|
{
|
||||||
|
QPointF startPoint(m_pickedRectangle.left(), rowPosition);
|
||||||
|
QPointF endPoint(m_pickedRectangle.right(), rowPosition);
|
||||||
|
|
||||||
|
painter->drawLine(pagePointToDevicePointMatrix.map(startPoint), pagePointToDevicePointMatrix.map(endPoint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::mousePressEvent(QWidget* widget, QMouseEvent* event)
|
||||||
|
{
|
||||||
|
BaseClass::mousePressEvent(widget, event);
|
||||||
|
|
||||||
|
if (event->isAccepted() || !isTablePicked())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton)
|
||||||
|
{
|
||||||
|
QPointF pagePoint;
|
||||||
|
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
|
||||||
|
if (pageIndex != -1 && pageIndex == m_pageIndex && m_pickedRectangle.contains(pagePoint))
|
||||||
|
{
|
||||||
|
const PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex);
|
||||||
|
bool isSelectingColumns = false;
|
||||||
|
|
||||||
|
const PageRotation rotation = getPageRotationCombined(page->getPageRotation(), getProxy()->getPageRotation());
|
||||||
|
switch (rotation)
|
||||||
|
{
|
||||||
|
case pdf::PageRotation::None:
|
||||||
|
case pdf::PageRotation::Rotate180:
|
||||||
|
isSelectingColumns = event->button() == Qt::LeftButton;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case pdf::PageRotation::Rotate90:
|
||||||
|
case pdf::PageRotation::Rotate270:
|
||||||
|
isSelectingColumns = event->button() == Qt::RightButton;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Q_ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFReal distanceThresholdPixels = PDFWidgetUtils::scaleDPI_x(widget, 7.0);
|
||||||
|
const PDFReal distanceThreshold = getProxy()->transformPixelToDeviceSpace(distanceThresholdPixels);
|
||||||
|
|
||||||
|
if (isSelectingColumns)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(m_horizontalBreaks.begin(), m_horizontalBreaks.end(), [distanceThreshold, pagePoint](const PDFReal value) { return qAbs(value - pagePoint.x()) < distanceThreshold; });
|
||||||
|
if (it != m_horizontalBreaks.end())
|
||||||
|
{
|
||||||
|
m_horizontalBreaks.erase(it);
|
||||||
|
}
|
||||||
|
else if (pagePoint.x() > m_pickedRectangle.left() + distanceThreshold && pagePoint.x() < m_pickedRectangle.right() - distanceThreshold)
|
||||||
|
{
|
||||||
|
m_horizontalBreaks.insert(std::lower_bound(m_horizontalBreaks.begin(), m_horizontalBreaks.end(), pagePoint.x()), pagePoint.x());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto it = std::find_if(m_verticalBreaks.begin(), m_verticalBreaks.end(), [distanceThreshold, pagePoint](const PDFReal value) { return qAbs(value - pagePoint.y()) < distanceThreshold; });
|
||||||
|
if (it != m_verticalBreaks.end())
|
||||||
|
{
|
||||||
|
m_verticalBreaks.erase(it);
|
||||||
|
}
|
||||||
|
else if (pagePoint.y() > m_pickedRectangle.top() + distanceThreshold && pagePoint.y() < m_pickedRectangle.bottom() - distanceThreshold)
|
||||||
|
{
|
||||||
|
m_verticalBreaks.insert(std::lower_bound(m_verticalBreaks.begin(), m_verticalBreaks.end(), pagePoint.y()), pagePoint.y());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit getProxy()->repaintNeeded();
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
|
||||||
|
{
|
||||||
|
BaseClass::mouseMoveEvent(widget, event);
|
||||||
|
|
||||||
|
if (!event->isAccepted() && isTablePicked())
|
||||||
|
{
|
||||||
|
QPointF pagePoint;
|
||||||
|
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
|
||||||
|
if (pageIndex != -1 && pageIndex == m_pageIndex && m_pickedRectangle.contains(pagePoint))
|
||||||
|
{
|
||||||
|
setCursor(Qt::CrossCursor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setCursor(Qt::ArrowCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(widget);
|
||||||
|
|
||||||
|
if (event == QKeySequence::Copy)
|
||||||
|
{
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::keyPressEvent(QWidget* widget, QKeyEvent* event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(widget);
|
||||||
|
|
||||||
|
if (event == QKeySequence::Copy ||
|
||||||
|
event->key() == Qt::Key_Return ||
|
||||||
|
event->key() == Qt::Key_Enter)
|
||||||
|
{
|
||||||
|
// Create table cells
|
||||||
|
struct TableCell
|
||||||
|
{
|
||||||
|
size_t row = 0;
|
||||||
|
size_t column = 0;
|
||||||
|
QRectF rectangle;
|
||||||
|
QString text;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TableCell> tableCells;
|
||||||
|
std::vector<PDFReal> horizontalBreaks = m_horizontalBreaks;
|
||||||
|
std::vector<PDFReal> verticalBreaks = m_verticalBreaks;
|
||||||
|
|
||||||
|
horizontalBreaks.insert(horizontalBreaks.begin(), m_pickedRectangle.left());
|
||||||
|
horizontalBreaks.push_back(m_pickedRectangle.right());
|
||||||
|
|
||||||
|
verticalBreaks.insert(verticalBreaks.begin(), m_pickedRectangle.top());
|
||||||
|
verticalBreaks.push_back(m_pickedRectangle.bottom());
|
||||||
|
|
||||||
|
tableCells.reserve((horizontalBreaks.size() - 1) * (verticalBreaks.size() - 1));
|
||||||
|
for (size_t rowIndex = 1; rowIndex < verticalBreaks.size(); ++rowIndex)
|
||||||
|
{
|
||||||
|
const PDFReal top = verticalBreaks[rowIndex - 1];
|
||||||
|
const PDFReal bottom = verticalBreaks[rowIndex];
|
||||||
|
const PDFReal height = bottom - top;
|
||||||
|
|
||||||
|
for (size_t columnIndex = 1; columnIndex < horizontalBreaks.size(); ++columnIndex)
|
||||||
|
{
|
||||||
|
const PDFReal left = horizontalBreaks[columnIndex - 1];
|
||||||
|
const PDFReal right = horizontalBreaks[columnIndex];
|
||||||
|
const PDFReal width = right - left;
|
||||||
|
|
||||||
|
TableCell cell;
|
||||||
|
cell.row = rowIndex;
|
||||||
|
cell.column = columnIndex;
|
||||||
|
cell.rectangle = QRectF(left, top, width, height);
|
||||||
|
tableCells.push_back(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFTextBlocks& textBlocks = m_textLayout.getTextBlocks();
|
||||||
|
for (size_t i = 0; i < textBlocks.size(); ++i)
|
||||||
|
{
|
||||||
|
// Detect, if whole block can be in some text cell
|
||||||
|
const PDFTextBlock& textBlock = textBlocks[i];
|
||||||
|
QRectF textRect = textBlock.getBoundingBox().boundingRect();
|
||||||
|
auto it = std::find_if(tableCells.begin(), tableCells.end(), [textRect](const auto& cell) { return cell.rectangle.contains(textRect); });
|
||||||
|
if (it != tableCells.end())
|
||||||
|
{
|
||||||
|
// Jakub Melka: whole block is contained in the cell
|
||||||
|
PDFTextSelection blockSelection = m_textLayout.selectBlock(i, m_pageIndex, QColor());
|
||||||
|
QString text = m_textLayout.getTextFromSelection(blockSelection, m_pageIndex);
|
||||||
|
TableCell& cell = *it;
|
||||||
|
cell.text = QString("%1 %2").arg(cell.text, text).trimmed();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFTextLines& textLines = textBlock.getLines();
|
||||||
|
for (size_t j = 0; j < textLines.size(); ++j)
|
||||||
|
{
|
||||||
|
const PDFTextLine& textLine = textLines[j];
|
||||||
|
QRectF boundingRect = textLine.getBoundingBox().boundingRect();
|
||||||
|
|
||||||
|
auto it = std::find_if(tableCells.begin(), tableCells.end(), [boundingRect](const auto& cell) { return cell.rectangle.contains(boundingRect); });
|
||||||
|
if (it != tableCells.end())
|
||||||
|
{
|
||||||
|
// Jakub Melka: whole block is contained in the cell
|
||||||
|
PDFTextSelection blockSelection = m_textLayout.selectLineInBlock(i, j, m_pageIndex, QColor());
|
||||||
|
QString text = m_textLayout.getTextFromSelection(blockSelection, m_pageIndex);
|
||||||
|
TableCell& cell = *it;
|
||||||
|
cell.text = QString("%1 %2").arg(cell.text, text).trimmed();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_isTransposed)
|
||||||
|
{
|
||||||
|
auto comparator = [](const TableCell& left, const TableCell right)
|
||||||
|
{
|
||||||
|
return std::make_pair(left.column, left.row) < std::make_pair(right.column, right.row);
|
||||||
|
};
|
||||||
|
std::sort(tableCells.begin(), tableCells.end(), comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make CSV string
|
||||||
|
QString string;
|
||||||
|
{
|
||||||
|
QTextStream stream(&string, QIODevice::WriteOnly | QIODevice::Text);
|
||||||
|
|
||||||
|
bool isFirst = true;
|
||||||
|
for (const TableCell& tableCell : tableCells)
|
||||||
|
{
|
||||||
|
if ((!m_isTransposed && tableCell.column == 1) ||
|
||||||
|
(m_isTransposed && tableCell.row == 1))
|
||||||
|
{
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream << Qt::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << tableCell.text << ";";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QApplication::clipboard()->setText(string);
|
||||||
|
|
||||||
|
setActive(false);
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::setActiveImpl(bool active)
|
||||||
|
{
|
||||||
|
BaseClass::setActiveImpl(active);
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
addTool(m_pickTool);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clear all data
|
||||||
|
setPageIndex(-1);
|
||||||
|
setPickedRectangle(QRectF());
|
||||||
|
setTextLayout(PDFTextLayout());
|
||||||
|
|
||||||
|
m_isTransposed = false;
|
||||||
|
m_horizontalBreaks.clear();
|
||||||
|
m_verticalBreaks.clear();
|
||||||
|
|
||||||
|
if (getTopToolstackTool())
|
||||||
|
{
|
||||||
|
removeTool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle)
|
||||||
|
{
|
||||||
|
removeTool();
|
||||||
|
|
||||||
|
setPageIndex(pageIndex);
|
||||||
|
setPickedRectangle(pageRectangle);
|
||||||
|
setTextLayout(getProxy()->getTextLayoutCompiler()->createTextLayout(pageIndex));
|
||||||
|
|
||||||
|
const PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex);
|
||||||
|
const PageRotation rotation = getPageRotationCombined(page->getPageRotation(), getProxy()->getPageRotation());
|
||||||
|
switch (rotation)
|
||||||
|
{
|
||||||
|
case pdf::PageRotation::None:
|
||||||
|
case pdf::PageRotation::Rotate180:
|
||||||
|
m_isTransposed = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case pdf::PageRotation::Rotate90:
|
||||||
|
case pdf::PageRotation::Rotate270:
|
||||||
|
m_isTransposed = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Q_ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
autodetectTableGeometry();
|
||||||
|
|
||||||
|
emit messageDisplayRequest(tr("Table region was selected. Use left/right mouse buttons to add/remove rows/columns, then use Enter key to copy the table."), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::autodetectTableGeometry()
|
||||||
|
{
|
||||||
|
// Strategy: for horizontal/vertical direction,
|
||||||
|
// detect columns/rows as follow: create overlap
|
||||||
|
// graph for each direction, detect overlap components.
|
||||||
|
// Then, remove "bridges" - rectangles overlapping
|
||||||
|
// two other rectangles, and these two rectangles
|
||||||
|
// does not overlap. These bridges are often header
|
||||||
|
// rows / columns.
|
||||||
|
|
||||||
|
// Detect text rectangles - divide them by lines
|
||||||
|
std::vector<QRectF> rectangles;
|
||||||
|
|
||||||
|
for (const PDFTextBlock& textBlock : m_textLayout.getTextBlocks())
|
||||||
|
{
|
||||||
|
for (const PDFTextLine& textLine : textBlock.getLines())
|
||||||
|
{
|
||||||
|
QRectF boundingRect = textLine.getBoundingBox().boundingRect();
|
||||||
|
|
||||||
|
if (!m_pickedRectangle.contains(boundingRect))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rectangles.push_back(boundingRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto createComponents = [&](bool isHorizontal) -> std::vector<QRectF>
|
||||||
|
{
|
||||||
|
std::vector<QRectF> resultComponents;
|
||||||
|
std::map<size_t, std::set<size_t>> isOverlappedGraph;
|
||||||
|
|
||||||
|
// Create graph of overlapped rectangles
|
||||||
|
for (size_t i = 0; i < rectangles.size(); ++i)
|
||||||
|
{
|
||||||
|
isOverlappedGraph[i].insert(i);
|
||||||
|
|
||||||
|
for (size_t j = i + 1; j < rectangles.size(); ++j)
|
||||||
|
{
|
||||||
|
const QRectF& leftRect = rectangles[i];
|
||||||
|
const QRectF& rightRect = rectangles[j];
|
||||||
|
|
||||||
|
if ((isHorizontal && isRectangleHorizontallyOverlapped(leftRect, rightRect)) ||
|
||||||
|
(!isHorizontal && isRectangleVerticallyOverlapped(leftRect, rightRect)))
|
||||||
|
{
|
||||||
|
isOverlappedGraph[i].insert(j);
|
||||||
|
isOverlappedGraph[j].insert(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<size_t> bridges;
|
||||||
|
|
||||||
|
// Detect bridges, i bridge <=> exist k,j, where isOverlappedGraph[i] has { k, j },
|
||||||
|
// and isOverlappedGraph[k] has not j. Second check is not neccessary, because
|
||||||
|
// graph is undirectional - if j is in isOverlappedGraph[k], then k is in isOverlappedGraph[j].
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rectangles.size(); ++i)
|
||||||
|
{
|
||||||
|
bool isBridge = false;
|
||||||
|
|
||||||
|
for (size_t k : isOverlappedGraph[i])
|
||||||
|
{
|
||||||
|
if (k == i)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j : isOverlappedGraph[i])
|
||||||
|
{
|
||||||
|
if (k == j)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOverlappedGraph[k].count(j))
|
||||||
|
{
|
||||||
|
isBridge = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBridge)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBridge)
|
||||||
|
{
|
||||||
|
bridges.insert(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove bridges from overlapped graph
|
||||||
|
for (const size_t i : bridges)
|
||||||
|
{
|
||||||
|
isOverlappedGraph.erase(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& item : isOverlappedGraph)
|
||||||
|
{
|
||||||
|
std::set<size_t> result;
|
||||||
|
std::set_difference(item.second.begin(), item.second.end(), bridges.begin(), bridges.end(), std::inserter(result, result.end()));
|
||||||
|
item.second = std::move(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, each component is a clique
|
||||||
|
std::set<size_t> visited;
|
||||||
|
|
||||||
|
for (auto& item : isOverlappedGraph)
|
||||||
|
{
|
||||||
|
if (visited.count(item.first))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
visited.insert(item.second.begin(), item.second.end());
|
||||||
|
|
||||||
|
QRectF boundingRectangle;
|
||||||
|
for (size_t i : item.second)
|
||||||
|
{
|
||||||
|
boundingRectangle = boundingRectangle.united(rectangles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boundingRectangle.isEmpty())
|
||||||
|
{
|
||||||
|
resultComponents.push_back(boundingRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultComponents;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
m_horizontalBreaks.clear();
|
||||||
|
std::vector<QRectF> columnComponents = createComponents(true);
|
||||||
|
std::sort(columnComponents.begin(), columnComponents.end(), [](const auto& left, const auto& right) { return left.center().x() < right.center().x(); });
|
||||||
|
for (size_t i = 1; i < columnComponents.size(); ++i)
|
||||||
|
{
|
||||||
|
const qreal start = columnComponents[i - 1].right();
|
||||||
|
const qreal end = columnComponents[i].left();
|
||||||
|
const qreal middle = (start + end) * 0.5;
|
||||||
|
m_horizontalBreaks.push_back(middle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rows
|
||||||
|
m_verticalBreaks.clear();
|
||||||
|
std::vector<QRectF> rowComponents = createComponents(false);
|
||||||
|
std::sort(rowComponents.begin(), rowComponents.end(), [](const auto& left, const auto& right) { return left.center().y() < right.center().y(); });
|
||||||
|
for (size_t i = 1; i < rowComponents.size(); ++i)
|
||||||
|
{
|
||||||
|
const qreal start = rowComponents[i - 1].bottom();
|
||||||
|
const qreal end = rowComponents[i].top();
|
||||||
|
const qreal middle = (start + end) * 0.5;
|
||||||
|
m_verticalBreaks.push_back(middle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PDFSelectTableTool::isTablePicked() const
|
||||||
|
{
|
||||||
|
return m_pageIndex != -1 && !m_pickedRectangle.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::setTextLayout(PDFTextLayout&& newTextLayout)
|
||||||
|
{
|
||||||
|
m_textLayout = std::move(newTextLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::setPageIndex(PDFInteger newPageIndex)
|
||||||
|
{
|
||||||
|
m_pageIndex = newPageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSelectTableTool::setPickedRectangle(const QRectF& newPickedRectangle)
|
||||||
|
{
|
||||||
|
m_pickedRectangle = newPickedRectangle;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@@ -373,6 +373,56 @@ private:
|
|||||||
QColor m_selectionRectangleColor;
|
QColor m_selectionRectangleColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Tool for selection of table in document. Rows and columns
|
||||||
|
/// are automatically detected and are modifiable by the user.
|
||||||
|
class PDFSelectTableTool : public PDFWidgetTool
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
using BaseClass = PDFWidgetTool;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Construct new table selection tool
|
||||||
|
/// \param proxy Draw widget proxy
|
||||||
|
/// \param action Tool activation action
|
||||||
|
/// \param parent Parent object
|
||||||
|
explicit PDFSelectTableTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent);
|
||||||
|
|
||||||
|
virtual void drawPage(QPainter* painter,
|
||||||
|
PDFInteger pageIndex,
|
||||||
|
const PDFPrecompiledPage* compiledPage,
|
||||||
|
PDFTextLayoutGetter& layoutGetter,
|
||||||
|
const QMatrix& pagePointToDevicePointMatrix,
|
||||||
|
QList<PDFRenderError>& errors) const override;
|
||||||
|
|
||||||
|
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
|
||||||
|
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
|
||||||
|
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override;
|
||||||
|
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void setActiveImpl(bool active) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setPickedRectangle(const QRectF& newPickedRectangle);
|
||||||
|
void setPageIndex(PDFInteger newPageIndex);
|
||||||
|
void setTextLayout(PDFTextLayout&& newTextLayout);
|
||||||
|
void autodetectTableGeometry();
|
||||||
|
|
||||||
|
bool isTablePicked() const;
|
||||||
|
|
||||||
|
void onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle);
|
||||||
|
|
||||||
|
PDFPickTool* m_pickTool;
|
||||||
|
PDFInteger m_pageIndex;
|
||||||
|
QRectF m_pickedRectangle;
|
||||||
|
PDFTextLayout m_textLayout;
|
||||||
|
bool m_isTransposed;
|
||||||
|
std::vector<PDFReal> m_horizontalBreaks;
|
||||||
|
std::vector<PDFReal> m_verticalBreaks;
|
||||||
|
};
|
||||||
|
|
||||||
/// Tool that makes screenshot of page area and copies it to the clipboard,
|
/// Tool that makes screenshot of page area and copies it to the clipboard,
|
||||||
/// using current client area to determine image size.
|
/// using current client area to determine image size.
|
||||||
class PDF4QTLIBSHARED_EXPORT PDFScreenshotTool : public PDFWidgetTool
|
class PDF4QTLIBSHARED_EXPORT PDFScreenshotTool : public PDFWidgetTool
|
||||||
@@ -436,6 +486,7 @@ public:
|
|||||||
QAction* findPrevAction = nullptr; ///< Action for navigating to previous result
|
QAction* findPrevAction = nullptr; ///< Action for navigating to previous result
|
||||||
QAction* findNextAction = nullptr; ///< Action for navigating to next result
|
QAction* findNextAction = nullptr; ///< Action for navigating to next result
|
||||||
QAction* selectTextToolAction = nullptr;
|
QAction* selectTextToolAction = nullptr;
|
||||||
|
QAction* selectTableToolAction = nullptr;
|
||||||
QAction* selectAllAction = nullptr;
|
QAction* selectAllAction = nullptr;
|
||||||
QAction* deselectAction = nullptr;
|
QAction* deselectAction = nullptr;
|
||||||
QAction* copyTextAction = nullptr;
|
QAction* copyTextAction = nullptr;
|
||||||
@@ -460,6 +511,7 @@ public:
|
|||||||
PickRectangleTool,
|
PickRectangleTool,
|
||||||
FindTextTool,
|
FindTextTool,
|
||||||
SelectTextTool,
|
SelectTextTool,
|
||||||
|
SelectTableTool,
|
||||||
MagnifierTool,
|
MagnifierTool,
|
||||||
ScreenshotTool,
|
ScreenshotTool,
|
||||||
ExtractImageTool,
|
ExtractImageTool,
|
||||||
|
@@ -91,5 +91,6 @@
|
|||||||
<file>resources/pce-same-size.svg</file>
|
<file>resources/pce-same-size.svg</file>
|
||||||
<file>resources/pce-same-width.svg</file>
|
<file>resources/pce-same-width.svg</file>
|
||||||
<file>resources/certificate-manager.svg</file>
|
<file>resources/certificate-manager.svg</file>
|
||||||
|
<file>resources/select-table.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@@ -44,7 +44,7 @@ PDFAboutDialog::PDFAboutDialog(QWidget* parent) :
|
|||||||
ui->tableWidget->setSelectionMode(QTableView::SingleSelection);
|
ui->tableWidget->setSelectionMode(QTableView::SingleSelection);
|
||||||
ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
|
|
||||||
for (size_t i = 0; i < infos.size(); ++i)
|
for (int i = 0; i < int(infos.size()); ++i)
|
||||||
{
|
{
|
||||||
const pdf::PDFDependentLibraryInfo& info = infos[i];
|
const pdf::PDFDependentLibraryInfo& info = infos[i];
|
||||||
ui->tableWidget->setItem(i, 0, new QTableWidgetItem(info.library));
|
ui->tableWidget->setItem(i, 0, new QTableWidgetItem(info.library));
|
||||||
|
@@ -939,6 +939,7 @@ void PDFProgramController::initializeToolManager()
|
|||||||
actions.findPrevAction = m_actionManager->getAction(PDFActionManager::FindPrevious);
|
actions.findPrevAction = m_actionManager->getAction(PDFActionManager::FindPrevious);
|
||||||
actions.findNextAction = m_actionManager->getAction(PDFActionManager::FindNext);
|
actions.findNextAction = m_actionManager->getAction(PDFActionManager::FindNext);
|
||||||
actions.selectTextToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectText);
|
actions.selectTextToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectText);
|
||||||
|
actions.selectTableToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectTable);
|
||||||
actions.selectAllAction = m_actionManager->getAction(PDFActionManager::SelectTextAll);
|
actions.selectAllAction = m_actionManager->getAction(PDFActionManager::SelectTextAll);
|
||||||
actions.deselectAction = m_actionManager->getAction(PDFActionManager::DeselectText);
|
actions.deselectAction = m_actionManager->getAction(PDFActionManager::DeselectText);
|
||||||
actions.copyTextAction = m_actionManager->getAction(PDFActionManager::CopyText);
|
actions.copyTextAction = m_actionManager->getAction(PDFActionManager::CopyText);
|
||||||
|
@@ -161,6 +161,7 @@ public:
|
|||||||
PageLayoutTwoColumns,
|
PageLayoutTwoColumns,
|
||||||
PageLayoutFirstPageOnRightSide,
|
PageLayoutFirstPageOnRightSide,
|
||||||
ToolSelectText,
|
ToolSelectText,
|
||||||
|
ToolSelectTable,
|
||||||
ToolMagnifier,
|
ToolMagnifier,
|
||||||
ToolScreenshot,
|
ToolScreenshot,
|
||||||
ToolExtractImage,
|
ToolExtractImage,
|
||||||
|
@@ -181,6 +181,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
m_actionManager->setAction(PDFActionManager::PageLayoutTwoColumns, ui->actionPageLayoutTwoColumns);
|
m_actionManager->setAction(PDFActionManager::PageLayoutTwoColumns, ui->actionPageLayoutTwoColumns);
|
||||||
m_actionManager->setAction(PDFActionManager::PageLayoutFirstPageOnRightSide, ui->actionFirstPageOnRightSide);
|
m_actionManager->setAction(PDFActionManager::PageLayoutFirstPageOnRightSide, ui->actionFirstPageOnRightSide);
|
||||||
m_actionManager->setAction(PDFActionManager::ToolSelectText, ui->actionSelectText);
|
m_actionManager->setAction(PDFActionManager::ToolSelectText, ui->actionSelectText);
|
||||||
|
m_actionManager->setAction(PDFActionManager::ToolSelectTable, ui->actionSelectTable);
|
||||||
m_actionManager->setAction(PDFActionManager::ToolMagnifier, ui->actionMagnifier);
|
m_actionManager->setAction(PDFActionManager::ToolMagnifier, ui->actionMagnifier);
|
||||||
m_actionManager->setAction(PDFActionManager::ToolScreenshot, ui->actionScreenshot);
|
m_actionManager->setAction(PDFActionManager::ToolScreenshot, ui->actionScreenshot);
|
||||||
m_actionManager->setAction(PDFActionManager::ToolExtractImage, ui->actionExtractImage);
|
m_actionManager->setAction(PDFActionManager::ToolExtractImage, ui->actionExtractImage);
|
||||||
@@ -238,6 +239,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
ui->mainToolBar->addAction(ui->actionSelectText);
|
ui->mainToolBar->addAction(ui->actionSelectText);
|
||||||
|
ui->mainToolBar->addAction(ui->actionSelectTable);
|
||||||
ui->mainToolBar->addAction(ui->actionCreateTextHighlight);
|
ui->mainToolBar->addAction(ui->actionCreateTextHighlight);
|
||||||
ui->mainToolBar->addAction(ui->actionCreateTextUnderline);
|
ui->mainToolBar->addAction(ui->actionCreateTextUnderline);
|
||||||
ui->mainToolBar->addAction(ui->actionCreateTextStrikeout);
|
ui->mainToolBar->addAction(ui->actionCreateTextStrikeout);
|
||||||
|
@@ -136,6 +136,8 @@
|
|||||||
<addaction name="actionSelectTextAll"/>
|
<addaction name="actionSelectTextAll"/>
|
||||||
<addaction name="actionDeselectText"/>
|
<addaction name="actionDeselectText"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionSelectTable"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEncryption"/>
|
<addaction name="actionEncryption"/>
|
||||||
<addaction name="actionOptimize"/>
|
<addaction name="actionOptimize"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
@@ -470,6 +472,18 @@
|
|||||||
<string>Select text</string>
|
<string>Select text</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionSelectTable">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="pdf4qtviewer.qrc">
|
||||||
|
<normaloff>:/resources/select-table.svg</normaloff>:/resources/select-table.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select table</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionSelectTextAll">
|
<action name="actionSelectTextAll">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="pdf4qtviewer.qrc">
|
<iconset resource="pdf4qtviewer.qrc">
|
||||||
|
39
Pdf4QtViewer/resources/select-table.svg
Normal file
39
Pdf4QtViewer/resources/select-table.svg
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Vrstva_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<polygon fill="#292D32" points="0.754,19.056 0.754,19.055 0.754,19.055 "/>
|
||||||
|
<path fill="#292D32" d="M20.458,5.659c-0.061-0.151-0.122-0.3-0.213-0.434c-0.406-0.61-1.025-0.918-1.845-0.918h-1.636h-1.634
|
||||||
|
L3.303,4.308c-0.939,0-1.704,0.484-2.058,1.273C1.217,5.7,1.069,6.356,1.052,6.851L1.047,19.676c0,0,0.025,0.778,0.159,1.18
|
||||||
|
c0.066,0.185,0.139,0.369,0.25,0.538c0.408,0.606,1.03,0.914,1.848,0.914h15.095c0.874-0.004,1.596-0.421,1.979-1.113
|
||||||
|
c0.192-0.407,0.255-1.249,0.271-1.523l0.006-12.733C20.653,6.424,20.485,5.768,20.458,5.659z M2.126,8.354c0-0.594,0-1.188,0-1.78
|
||||||
|
c0.001-0.748,0.431-1.179,1.178-1.179h7.548h7.551c0.743,0,1.171,0.432,1.171,1.183c0.002,0.554,0,1.107,0,1.661v0.887H2.126V8.354z
|
||||||
|
M19.562,10.24v2.922h-3.729V10.24H19.562z M5.867,10.235v2.919H2.142v-2.919H5.867z M5.868,21.218
|
||||||
|
C5.855,21.22,5.844,21.22,5.832,21.22c-0.848,0-1.697,0-2.545-0.002c-0.712,0-1.157-0.443-1.16-1.154c0-0.406,0-0.812,0-1.216
|
||||||
|
v-0.561h3.742L5.868,21.218L5.868,21.218z M5.875,17.182H2.144v-2.919h3.731V17.182z M6.967,10.243h3.327v2.914H6.967V10.243z
|
||||||
|
M10.293,21.206H6.968v-2.912h3.325V21.206z M10.299,17.182H6.97v-2.919h3.329V17.182z M14.725,21.202h-3.319v-2.918h3.319V21.202z
|
||||||
|
M14.73,17.184h-3.325V14.26h3.325V17.184z M14.73,13.157h-3.321v-2.921h3.321V13.157z M15.828,14.265h3.729v2.921h-3.729V14.265z
|
||||||
|
M19.573,19.306v0.259c0,0.259,0,0.526-0.03,0.781c-0.055,0.469-0.485,0.858-0.96,0.865c-0.471,0.008-0.941,0.01-1.413,0.01
|
||||||
|
l-1.338-0.002v-2.928h3.742l0.002,0.252C19.575,18.8,19.577,19.053,19.573,19.306z"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M21.506,5.837c-0.144,0.036-0.272,0.067-0.401,0.097
|
||||||
|
c-0.156,0.037-0.275-0.065-0.264-0.225c0.007-0.087,0.064-0.15,0.16-0.174c0.207-0.05,0.412-0.098,0.618-0.147
|
||||||
|
c0.074-0.018,0.147-0.035,0.221-0.052c0.125-0.03,0.232,0.034,0.264,0.16c0.068,0.279,0.136,0.561,0.202,0.84
|
||||||
|
c0.03,0.126-0.03,0.227-0.147,0.255c-0.123,0.029-0.222-0.032-0.253-0.16c-0.025-0.104-0.051-0.209-0.075-0.313
|
||||||
|
c-0.001-0.002-0.005-0.005-0.009-0.01c-0.039,0.042-0.075,0.086-0.114,0.126c-0.482,0.497-1.07,0.728-1.759,0.672
|
||||||
|
c-0.953-0.077-1.74-0.78-1.938-1.715c-0.015-0.071-0.025-0.142-0.032-0.214c-0.011-0.123,0.067-0.216,0.183-0.228
|
||||||
|
c0.114-0.012,0.209,0.064,0.224,0.185c0.044,0.37,0.181,0.701,0.434,0.978c0.385,0.424,0.868,0.624,1.441,0.584
|
||||||
|
c0.486-0.033,0.888-0.243,1.207-0.611C21.478,5.874,21.487,5.862,21.506,5.837z"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M18.766,3.757c0.104-0.024,0.192-0.046,0.283-0.067
|
||||||
|
c0.044-0.011,0.088-0.022,0.131-0.031c0.114-0.024,0.211,0.04,0.239,0.154c0.028,0.11-0.029,0.217-0.14,0.244
|
||||||
|
c-0.291,0.072-0.583,0.141-0.875,0.208c-0.1,0.023-0.204-0.042-0.229-0.146c-0.073-0.29-0.144-0.582-0.211-0.874
|
||||||
|
c-0.024-0.103,0.049-0.207,0.153-0.233c0.105-0.026,0.216,0.035,0.246,0.143c0.028,0.109,0.053,0.22,0.081,0.338
|
||||||
|
c0.036-0.041,0.067-0.076,0.1-0.111c0.322-0.349,0.724-0.553,1.185-0.653c0.93-0.204,2.262,0.36,2.535,1.691
|
||||||
|
c0.012,0.059,0.02,0.12,0.029,0.181c0.02,0.13-0.048,0.229-0.169,0.247c-0.131,0.02-0.225-0.056-0.24-0.193
|
||||||
|
c-0.021-0.188-0.066-0.37-0.15-0.54c-0.283-0.582-0.744-0.931-1.388-1.009c-0.616-0.074-1.132,0.139-1.543,0.604
|
||||||
|
C18.792,3.721,18.783,3.734,18.766,3.757z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
Reference in New Issue
Block a user