Bugfixing of text layout

This commit is contained in:
Jakub Melka 2019-12-29 17:25:18 +01:00
parent 0c97e21f54
commit afbf37d068
9 changed files with 125 additions and 8 deletions

View File

@ -25,6 +25,7 @@
#include "pdfcms.h"
#include <QPainter>
#include <QFontMetrics>
namespace pdf
{
@ -622,6 +623,55 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect);
compiledPage->draw(painter, page->getCropBox(), matrix, m_features);
// Draw text blocks/text lines, if it is enabled
if (m_features.testFlag(PDFRenderer::DebugTextBlocks))
{
const PDFTextLayout& layout = compiledPage->getTextLayout();
const PDFTextBlocks& textBlocks = layout.getTextBlocks();
painter->save();
painter->setFont(m_widget->font());
painter->setPen(Qt::red);
painter->setBrush(QColor(255, 0, 0, 128));
QFontMetricsF fontMetrics(painter->font(), painter->device());
int blockIndex = 1;
for (const PDFTextBlock& block : textBlocks)
{
QString blockNumber = QString::number(blockIndex++);
painter->drawPath(matrix.map(block.getBoundingBox()));
painter->drawText(matrix.map(block.getTopLeft()) - QPointF(fontMetrics.width(blockNumber), 0), blockNumber, Qt::TextSingleLine, 0);
}
painter->restore();
}
if (m_features.testFlag(PDFRenderer::DebugTextLines))
{
const PDFTextLayout& layout = compiledPage->getTextLayout();
const PDFTextBlocks& textBlocks = layout.getTextBlocks();
painter->save();
painter->setFont(m_widget->font());
painter->setPen(Qt::green);
painter->setBrush(QColor(0, 255, 0, 128));
QFontMetricsF fontMetrics(painter->font(), painter->device());
int lineIndex = 1;
for (const PDFTextBlock& block : textBlocks)
{
for (const PDFTextLine& line : block.getLines())
{
QString lineNumber = QString::number(lineIndex++);
painter->drawPath(matrix.map(line.getBoundingBox()));
painter->drawText(matrix.map(line.getTopLeft()) - QPointF(fontMetrics.width(lineNumber), 0), lineNumber, Qt::TextSingleLine, 0);
}
}
painter->restore();
}
const qint64 drawTimeNS = timer.nsecsElapsed();
// Draw rendering times

View File

@ -217,6 +217,9 @@ public:
/// Returns memory consumption estimate
qint64 getMemoryConsumptionEstimate() const { return m_memoryConsumptionEstimate; }
/// Returns text layout of the page
const PDFTextLayout& getTextLayout() const { return m_textLayout; }
private:
struct PathPaintData
{

View File

@ -49,6 +49,8 @@ public:
IgnoreOptionalContent = 0x0008, ///< Ignore optional content (so all is drawn ignoring settings of optional content)
ClipToCropBox = 0x0010, ///< Clip page content to crop box (items outside crop box will not be visible)
DisplayTimes = 0x0020, ///< Display page compile/draw time
DebugTextBlocks = 0x0040, ///< Debug text block layout algorithm
DebugTextLines = 0x0080, ///< Debug text line layout algorithm
};
Q_DECLARE_FLAGS(Features, Feature)

View File

@ -261,7 +261,7 @@ void PDFTextLayout::performDoLayout(PDFReal angle)
QRectF aBB = blocks[aIndex].getBoundingBox().boundingRect();
QRectF bBB = blocks[bIndex].getBoundingBox().boundingRect();
const bool isOverlappedOnHorizontalAxis = (aBB.right() < bBB.left() && aBB.left() < bBB.right()) || (bBB.right() < aBB.left() && bBB.left() < aBB.right());
const bool isOverlappedOnHorizontalAxis = isRectangleHorizontallyOverlapped(aBB, bBB);
const bool isAoverB = aBB.bottom() > bBB.top();
return isOverlappedOnHorizontalAxis && isAoverB;
};
@ -284,8 +284,8 @@ void PDFTextLayout::performDoLayout(PDFReal angle)
QRectF cBB = blocks[i].getBoundingBox().boundingRect();
if (cBB.top() >= abBB.top() && cBB.bottom() <= abBB.bottom())
{
const bool isAOverlappedOnHorizontalAxis = (aBB.right() < cBB.left() && aBB.left() < cBB.right()) || (cBB.right() < aBB.left() && cBB.left() < aBB.right());
const bool isBOverlappedOnHorizontalAxis = (cBB.right() < bBB.left() && cBB.left() < bBB.right()) || (bBB.right() < cBB.left() && bBB.left() < cBB.right());
const bool isAOverlappedOnHorizontalAxis = isRectangleHorizontallyOverlapped(aBB, cBB);
const bool isBOverlappedOnHorizontalAxis = isRectangleHorizontallyOverlapped(bBB, cBB);
if (isAOverlappedOnHorizontalAxis && isBOverlappedOnHorizontalAxis)
{
return false;
@ -310,7 +310,7 @@ void PDFTextLayout::performDoLayout(PDFReal angle)
workBlocks.insert(workBlocks.end(), i);
for (size_t j = 0; j < blocks.size(); ++j)
{
if (isBeforeByRule1(j, i) || isBeforeByRule2(j, i))
if (i != j && (isBeforeByRule1(j, i) || isBeforeByRule2(j, i)))
{
orderingEdges[i].insert(j);
}
@ -361,11 +361,13 @@ PDFTextLine::PDFTextLine(TextCharacters characters) :
boundingBox = boundingBox.united(character.boundingBox.boundingRect());
}
m_boundingBox.addRect(boundingBox);
m_topLeft = boundingBox.topLeft();
}
void PDFTextLine::applyTransform(const QMatrix& matrix)
{
m_boundingBox = matrix.map(m_boundingBox);
m_topLeft = matrix.map(m_topLeft);
for (TextCharacter& character : m_characters)
{
character.applyTransform(matrix);
@ -383,7 +385,7 @@ PDFTextBlock::PDFTextBlock(PDFTextLines textLines) :
const PDFReal xR = br.x();
const PDFReal yL = qRound(bl.y() * 100.0);
const PDFReal yR = qRound(br.y() * 100.0);
return std::tie(yL, xL) < std::tie(yR, xR);
return std::tie(-yL, xL) < std::tie(-yR, xR);
};
std::sort(m_lines.begin(), m_lines.end(), sortFunction);
@ -393,11 +395,13 @@ PDFTextBlock::PDFTextBlock(PDFTextLines textLines) :
boundingBox = boundingBox.united(line.getBoundingBox().boundingRect());
}
m_boundingBox.addRect(boundingBox);
m_topLeft = boundingBox.topLeft();
}
void PDFTextBlock::applyTransform(const QMatrix& matrix)
{
m_boundingBox = matrix.map(m_boundingBox);
m_topLeft = matrix.map(m_topLeft);
for (PDFTextLine& textLine : m_lines)
{
textLine.applyTransform(matrix);

View File

@ -104,12 +104,14 @@ public:
const TextCharacters& getCharacters() const { return m_characters; }
const QPainterPath& getBoundingBox() const { return m_boundingBox; }
const QPointF& getTopLeft() const { return m_topLeft; }
void applyTransform(const QMatrix& matrix);
private:
TextCharacters m_characters;
QPainterPath m_boundingBox;
QPointF m_topLeft;
};
using PDFTextLines = std::vector<PDFTextLine>;
@ -122,12 +124,14 @@ public:
const PDFTextLines& getLines() const { return m_lines; }
const QPainterPath& getBoundingBox() const { return m_boundingBox; }
const QPointF& getTopLeft() const { return m_topLeft; }
void applyTransform(const QMatrix& matrix);
private:
PDFTextLines m_lines;
QPainterPath m_boundingBox;
QPointF m_topLeft;
};
using PDFTextBlocks = std::vector<PDFTextBlock>;
@ -151,6 +155,9 @@ public:
/// Returns estimate of number of bytes, which this mesh occupies in memory
qint64 getMemoryConsumptionEstimate() const;
/// Returns recognized text blocks
const PDFTextBlocks& getTextBlocks() const { return m_blocks; }
private:
/// Makes layout for particular angle
void performDoLayout(PDFReal angle);

View File

@ -21,6 +21,7 @@
#include "pdfglobal.h"
#include <QRectF>
#include <QByteArray>
#include <QDataStream>
@ -407,6 +408,27 @@ private:
std::vector<T> m_indices;
};
template<typename T>
constexpr bool isIntervalOverlap(T x1_min, T x1_max, T x2_min, T x2_max)
{
// We have two situations, where intervals doesn't overlap:
// 1) |--------| |---------|
// x1_min x1_max x2_min x2_max
// 2) |--------| |---------|
// x2_min x2_max x1_min x1_max
if (x1_max < x2_min || x2_max < x1_min)
{
return false;
}
return true;
}
constexpr bool isRectangleHorizontallyOverlapped(const QRectF& r1, const QRectF& r2)
{
return isIntervalOverlap(r1.left(), r1.right(), r2.left(), r2.right());
}
} // namespace pdf
#endif // PDFUTILS_H

View File

@ -173,6 +173,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
ui->actionRenderOptionTextAntialiasing->setData(pdf::PDFRenderer::TextAntialiasing);
ui->actionRenderOptionSmoothPictures->setData(pdf::PDFRenderer::SmoothImages);
ui->actionRenderOptionIgnoreOptionalContentSettings->setData(pdf::PDFRenderer::IgnoreOptionalContent);
ui->actionShow_Text_Blocks->setData(pdf::PDFRenderer::DebugTextBlocks);
ui->actionShow_Text_Lines->setData(pdf::PDFRenderer::DebugTextLines);
for (QAction* action : getRenderingOptionActions())
{
@ -869,7 +871,12 @@ void PDFViewerMainWindow::setPageLayout(pdf::PageLayout pageLayout)
std::vector<QAction*> PDFViewerMainWindow::getRenderingOptionActions() const
{
return { ui->actionRenderOptionAntialiasing, ui->actionRenderOptionTextAntialiasing, ui->actionRenderOptionSmoothPictures, ui->actionRenderOptionIgnoreOptionalContentSettings };
return { ui->actionRenderOptionAntialiasing,
ui->actionRenderOptionTextAntialiasing,
ui->actionRenderOptionSmoothPictures,
ui->actionRenderOptionIgnoreOptionalContentSettings,
ui->actionShow_Text_Blocks,
ui->actionShow_Text_Lines };
}
QList<QAction*> PDFViewerMainWindow::getActions() const

View File

@ -85,9 +85,7 @@ private slots:
void on_actionFitPage_triggered();
void on_actionFitWidth_triggered();
void on_actionFitHeight_triggered();
void on_actionProperties_triggered();
void on_actionSend_by_E_Mail_triggered();
private:

View File

@ -89,11 +89,19 @@
</property>
<addaction name="actionAbout"/>
</widget>
<widget class="QMenu" name="menuDeveloper">
<property name="title">
<string>Developer</string>
</property>
<addaction name="actionShow_Text_Blocks"/>
<addaction name="actionShow_Text_Lines"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
<addaction name="menuGoTo"/>
<addaction name="menuTools"/>
<addaction name="menuHelp"/>
<addaction name="menuDeveloper"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
@ -316,6 +324,22 @@
<string>Send by E-Mail</string>
</property>
</action>
<action name="actionShow_Text_Blocks">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Text Blocks</string>
</property>
</action>
<action name="actionShow_Text_Lines">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Text Lines</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>