mirror of https://github.com/JakubMelka/PDF4QT.git
Merge remote-tracking branch 'remotes/origin/issue21'
This commit is contained in:
commit
b30150a503
|
@ -351,6 +351,7 @@ public:
|
|||
PDFWidget* getWidget() const { return m_widget; }
|
||||
bool isUsingOpenGL() const { return m_useOpenGL; }
|
||||
const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; }
|
||||
PageRotation getPageRotation() const { return m_controller->getPageRotation(); }
|
||||
|
||||
void setFeatures(PDFRenderer::Features features);
|
||||
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
||||
|
|
|
@ -469,6 +469,70 @@ PDFTextSelection PDFTextLayout::createTextSelection(PDFInteger pageIndex, const
|
|||
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
|
||||
{
|
||||
QStringList text;
|
||||
|
|
|
@ -381,6 +381,19 @@ public:
|
|||
/// \param pageIndex Index of the page
|
||||
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, PDFTextLayout& layout);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "pdfdrawwidget.h"
|
||||
#include "pdfcompiler.h"
|
||||
#include "pdfwidgetutils.h"
|
||||
#include "pdfpainterutils.h"
|
||||
#include "pdfdbgheap.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
@ -745,6 +746,7 @@ PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObje
|
|||
m_predefinedTools[PickRectangleTool] = pickTool;
|
||||
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[SelectTableTool] = new PDFSelectTableTool(proxy, actions.selectTableToolAction, this);
|
||||
m_predefinedTools[MagnifierTool] = new PDFMagnifierTool(proxy, actions.magnifierAction, this);
|
||||
m_predefinedTools[ScreenshotTool] = new PDFScreenshotTool(proxy, actions.screenshotToolAction, 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
|
||||
|
|
|
@ -373,6 +373,56 @@ private:
|
|||
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,
|
||||
/// using current client area to determine image size.
|
||||
class PDF4QTLIBSHARED_EXPORT PDFScreenshotTool : public PDFWidgetTool
|
||||
|
@ -436,6 +486,7 @@ public:
|
|||
QAction* findPrevAction = nullptr; ///< Action for navigating to previous result
|
||||
QAction* findNextAction = nullptr; ///< Action for navigating to next result
|
||||
QAction* selectTextToolAction = nullptr;
|
||||
QAction* selectTableToolAction = nullptr;
|
||||
QAction* selectAllAction = nullptr;
|
||||
QAction* deselectAction = nullptr;
|
||||
QAction* copyTextAction = nullptr;
|
||||
|
@ -460,6 +511,7 @@ public:
|
|||
PickRectangleTool,
|
||||
FindTextTool,
|
||||
SelectTextTool,
|
||||
SelectTableTool,
|
||||
MagnifierTool,
|
||||
ScreenshotTool,
|
||||
ExtractImageTool,
|
||||
|
|
|
@ -91,5 +91,6 @@
|
|||
<file>resources/pce-same-size.svg</file>
|
||||
<file>resources/pce-same-width.svg</file>
|
||||
<file>resources/certificate-manager.svg</file>
|
||||
<file>resources/select-table.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -44,7 +44,7 @@ PDFAboutDialog::PDFAboutDialog(QWidget* parent) :
|
|||
ui->tableWidget->setSelectionMode(QTableView::SingleSelection);
|
||||
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];
|
||||
ui->tableWidget->setItem(i, 0, new QTableWidgetItem(info.library));
|
||||
|
|
|
@ -939,6 +939,7 @@ void PDFProgramController::initializeToolManager()
|
|||
actions.findPrevAction = m_actionManager->getAction(PDFActionManager::FindPrevious);
|
||||
actions.findNextAction = m_actionManager->getAction(PDFActionManager::FindNext);
|
||||
actions.selectTextToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectText);
|
||||
actions.selectTableToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectTable);
|
||||
actions.selectAllAction = m_actionManager->getAction(PDFActionManager::SelectTextAll);
|
||||
actions.deselectAction = m_actionManager->getAction(PDFActionManager::DeselectText);
|
||||
actions.copyTextAction = m_actionManager->getAction(PDFActionManager::CopyText);
|
||||
|
|
|
@ -161,6 +161,7 @@ public:
|
|||
PageLayoutTwoColumns,
|
||||
PageLayoutFirstPageOnRightSide,
|
||||
ToolSelectText,
|
||||
ToolSelectTable,
|
||||
ToolMagnifier,
|
||||
ToolScreenshot,
|
||||
ToolExtractImage,
|
||||
|
|
|
@ -181,6 +181,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||
m_actionManager->setAction(PDFActionManager::PageLayoutTwoColumns, ui->actionPageLayoutTwoColumns);
|
||||
m_actionManager->setAction(PDFActionManager::PageLayoutFirstPageOnRightSide, ui->actionFirstPageOnRightSide);
|
||||
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::ToolScreenshot, ui->actionScreenshot);
|
||||
m_actionManager->setAction(PDFActionManager::ToolExtractImage, ui->actionExtractImage);
|
||||
|
@ -238,6 +239,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||
|
||||
// Tools
|
||||
ui->mainToolBar->addAction(ui->actionSelectText);
|
||||
ui->mainToolBar->addAction(ui->actionSelectTable);
|
||||
ui->mainToolBar->addAction(ui->actionCreateTextHighlight);
|
||||
ui->mainToolBar->addAction(ui->actionCreateTextUnderline);
|
||||
ui->mainToolBar->addAction(ui->actionCreateTextStrikeout);
|
||||
|
|
|
@ -136,6 +136,8 @@
|
|||
<addaction name="actionSelectTextAll"/>
|
||||
<addaction name="actionDeselectText"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelectTable"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEncryption"/>
|
||||
<addaction name="actionOptimize"/>
|
||||
<addaction name="separator"/>
|
||||
|
@ -470,6 +472,18 @@
|
|||
<string>Select text</string>
|
||||
</property>
|
||||
</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">
|
||||
<property name="icon">
|
||||
<iconset resource="pdf4qtviewer.qrc">
|
||||
|
|
|
@ -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 |
Loading…
Reference in New Issue