mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Bugfixing of text layout
This commit is contained in:
@ -25,6 +25,7 @@
|
|||||||
#include "pdfcms.h"
|
#include "pdfcms.h"
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -622,6 +623,55 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
|
|||||||
QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect);
|
QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect);
|
||||||
compiledPage->draw(painter, page->getCropBox(), matrix, m_features);
|
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();
|
const qint64 drawTimeNS = timer.nsecsElapsed();
|
||||||
|
|
||||||
// Draw rendering times
|
// Draw rendering times
|
||||||
|
@ -217,6 +217,9 @@ public:
|
|||||||
/// Returns memory consumption estimate
|
/// Returns memory consumption estimate
|
||||||
qint64 getMemoryConsumptionEstimate() const { return m_memoryConsumptionEstimate; }
|
qint64 getMemoryConsumptionEstimate() const { return m_memoryConsumptionEstimate; }
|
||||||
|
|
||||||
|
/// Returns text layout of the page
|
||||||
|
const PDFTextLayout& getTextLayout() const { return m_textLayout; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PathPaintData
|
struct PathPaintData
|
||||||
{
|
{
|
||||||
|
@ -49,6 +49,8 @@ public:
|
|||||||
IgnoreOptionalContent = 0x0008, ///< Ignore optional content (so all is drawn ignoring settings of optional content)
|
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)
|
ClipToCropBox = 0x0010, ///< Clip page content to crop box (items outside crop box will not be visible)
|
||||||
DisplayTimes = 0x0020, ///< Display page compile/draw time
|
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)
|
Q_DECLARE_FLAGS(Features, Feature)
|
||||||
|
@ -261,7 +261,7 @@ void PDFTextLayout::performDoLayout(PDFReal angle)
|
|||||||
QRectF aBB = blocks[aIndex].getBoundingBox().boundingRect();
|
QRectF aBB = blocks[aIndex].getBoundingBox().boundingRect();
|
||||||
QRectF bBB = blocks[bIndex].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();
|
const bool isAoverB = aBB.bottom() > bBB.top();
|
||||||
return isOverlappedOnHorizontalAxis && isAoverB;
|
return isOverlappedOnHorizontalAxis && isAoverB;
|
||||||
};
|
};
|
||||||
@ -284,8 +284,8 @@ void PDFTextLayout::performDoLayout(PDFReal angle)
|
|||||||
QRectF cBB = blocks[i].getBoundingBox().boundingRect();
|
QRectF cBB = blocks[i].getBoundingBox().boundingRect();
|
||||||
if (cBB.top() >= abBB.top() && cBB.bottom() <= abBB.bottom())
|
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 isAOverlappedOnHorizontalAxis = isRectangleHorizontallyOverlapped(aBB, cBB);
|
||||||
const bool isBOverlappedOnHorizontalAxis = (cBB.right() < bBB.left() && cBB.left() < bBB.right()) || (bBB.right() < cBB.left() && bBB.left() < cBB.right());
|
const bool isBOverlappedOnHorizontalAxis = isRectangleHorizontallyOverlapped(bBB, cBB);
|
||||||
if (isAOverlappedOnHorizontalAxis && isBOverlappedOnHorizontalAxis)
|
if (isAOverlappedOnHorizontalAxis && isBOverlappedOnHorizontalAxis)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -310,7 +310,7 @@ void PDFTextLayout::performDoLayout(PDFReal angle)
|
|||||||
workBlocks.insert(workBlocks.end(), i);
|
workBlocks.insert(workBlocks.end(), i);
|
||||||
for (size_t j = 0; j < blocks.size(); ++j)
|
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);
|
orderingEdges[i].insert(j);
|
||||||
}
|
}
|
||||||
@ -361,11 +361,13 @@ PDFTextLine::PDFTextLine(TextCharacters characters) :
|
|||||||
boundingBox = boundingBox.united(character.boundingBox.boundingRect());
|
boundingBox = boundingBox.united(character.boundingBox.boundingRect());
|
||||||
}
|
}
|
||||||
m_boundingBox.addRect(boundingBox);
|
m_boundingBox.addRect(boundingBox);
|
||||||
|
m_topLeft = boundingBox.topLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDFTextLine::applyTransform(const QMatrix& matrix)
|
void PDFTextLine::applyTransform(const QMatrix& matrix)
|
||||||
{
|
{
|
||||||
m_boundingBox = matrix.map(m_boundingBox);
|
m_boundingBox = matrix.map(m_boundingBox);
|
||||||
|
m_topLeft = matrix.map(m_topLeft);
|
||||||
for (TextCharacter& character : m_characters)
|
for (TextCharacter& character : m_characters)
|
||||||
{
|
{
|
||||||
character.applyTransform(matrix);
|
character.applyTransform(matrix);
|
||||||
@ -383,7 +385,7 @@ PDFTextBlock::PDFTextBlock(PDFTextLines textLines) :
|
|||||||
const PDFReal xR = br.x();
|
const PDFReal xR = br.x();
|
||||||
const PDFReal yL = qRound(bl.y() * 100.0);
|
const PDFReal yL = qRound(bl.y() * 100.0);
|
||||||
const PDFReal yR = qRound(br.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);
|
std::sort(m_lines.begin(), m_lines.end(), sortFunction);
|
||||||
|
|
||||||
@ -393,11 +395,13 @@ PDFTextBlock::PDFTextBlock(PDFTextLines textLines) :
|
|||||||
boundingBox = boundingBox.united(line.getBoundingBox().boundingRect());
|
boundingBox = boundingBox.united(line.getBoundingBox().boundingRect());
|
||||||
}
|
}
|
||||||
m_boundingBox.addRect(boundingBox);
|
m_boundingBox.addRect(boundingBox);
|
||||||
|
m_topLeft = boundingBox.topLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDFTextBlock::applyTransform(const QMatrix& matrix)
|
void PDFTextBlock::applyTransform(const QMatrix& matrix)
|
||||||
{
|
{
|
||||||
m_boundingBox = matrix.map(m_boundingBox);
|
m_boundingBox = matrix.map(m_boundingBox);
|
||||||
|
m_topLeft = matrix.map(m_topLeft);
|
||||||
for (PDFTextLine& textLine : m_lines)
|
for (PDFTextLine& textLine : m_lines)
|
||||||
{
|
{
|
||||||
textLine.applyTransform(matrix);
|
textLine.applyTransform(matrix);
|
||||||
|
@ -104,12 +104,14 @@ public:
|
|||||||
|
|
||||||
const TextCharacters& getCharacters() const { return m_characters; }
|
const TextCharacters& getCharacters() const { return m_characters; }
|
||||||
const QPainterPath& getBoundingBox() const { return m_boundingBox; }
|
const QPainterPath& getBoundingBox() const { return m_boundingBox; }
|
||||||
|
const QPointF& getTopLeft() const { return m_topLeft; }
|
||||||
|
|
||||||
void applyTransform(const QMatrix& matrix);
|
void applyTransform(const QMatrix& matrix);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextCharacters m_characters;
|
TextCharacters m_characters;
|
||||||
QPainterPath m_boundingBox;
|
QPainterPath m_boundingBox;
|
||||||
|
QPointF m_topLeft;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PDFTextLines = std::vector<PDFTextLine>;
|
using PDFTextLines = std::vector<PDFTextLine>;
|
||||||
@ -122,12 +124,14 @@ public:
|
|||||||
|
|
||||||
const PDFTextLines& getLines() const { return m_lines; }
|
const PDFTextLines& getLines() const { return m_lines; }
|
||||||
const QPainterPath& getBoundingBox() const { return m_boundingBox; }
|
const QPainterPath& getBoundingBox() const { return m_boundingBox; }
|
||||||
|
const QPointF& getTopLeft() const { return m_topLeft; }
|
||||||
|
|
||||||
void applyTransform(const QMatrix& matrix);
|
void applyTransform(const QMatrix& matrix);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PDFTextLines m_lines;
|
PDFTextLines m_lines;
|
||||||
QPainterPath m_boundingBox;
|
QPainterPath m_boundingBox;
|
||||||
|
QPointF m_topLeft;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PDFTextBlocks = std::vector<PDFTextBlock>;
|
using PDFTextBlocks = std::vector<PDFTextBlock>;
|
||||||
@ -151,6 +155,9 @@ public:
|
|||||||
/// Returns estimate of number of bytes, which this mesh occupies in memory
|
/// Returns estimate of number of bytes, which this mesh occupies in memory
|
||||||
qint64 getMemoryConsumptionEstimate() const;
|
qint64 getMemoryConsumptionEstimate() const;
|
||||||
|
|
||||||
|
/// Returns recognized text blocks
|
||||||
|
const PDFTextBlocks& getTextBlocks() const { return m_blocks; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Makes layout for particular angle
|
/// Makes layout for particular angle
|
||||||
void performDoLayout(PDFReal angle);
|
void performDoLayout(PDFReal angle);
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "pdfglobal.h"
|
#include "pdfglobal.h"
|
||||||
|
|
||||||
|
#include <QRectF>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
|
||||||
@ -407,6 +408,27 @@ private:
|
|||||||
std::vector<T> m_indices;
|
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
|
} // namespace pdf
|
||||||
|
|
||||||
#endif // PDFUTILS_H
|
#endif // PDFUTILS_H
|
||||||
|
@ -173,6 +173,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
ui->actionRenderOptionTextAntialiasing->setData(pdf::PDFRenderer::TextAntialiasing);
|
ui->actionRenderOptionTextAntialiasing->setData(pdf::PDFRenderer::TextAntialiasing);
|
||||||
ui->actionRenderOptionSmoothPictures->setData(pdf::PDFRenderer::SmoothImages);
|
ui->actionRenderOptionSmoothPictures->setData(pdf::PDFRenderer::SmoothImages);
|
||||||
ui->actionRenderOptionIgnoreOptionalContentSettings->setData(pdf::PDFRenderer::IgnoreOptionalContent);
|
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())
|
for (QAction* action : getRenderingOptionActions())
|
||||||
{
|
{
|
||||||
@ -869,7 +871,12 @@ void PDFViewerMainWindow::setPageLayout(pdf::PageLayout pageLayout)
|
|||||||
|
|
||||||
std::vector<QAction*> PDFViewerMainWindow::getRenderingOptionActions() const
|
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
|
QList<QAction*> PDFViewerMainWindow::getActions() const
|
||||||
|
@ -85,9 +85,7 @@ private slots:
|
|||||||
void on_actionFitPage_triggered();
|
void on_actionFitPage_triggered();
|
||||||
void on_actionFitWidth_triggered();
|
void on_actionFitWidth_triggered();
|
||||||
void on_actionFitHeight_triggered();
|
void on_actionFitHeight_triggered();
|
||||||
|
|
||||||
void on_actionProperties_triggered();
|
void on_actionProperties_triggered();
|
||||||
|
|
||||||
void on_actionSend_by_E_Mail_triggered();
|
void on_actionSend_by_E_Mail_triggered();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -89,11 +89,19 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="actionAbout"/>
|
<addaction name="actionAbout"/>
|
||||||
</widget>
|
</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="menuFile"/>
|
||||||
<addaction name="menuView"/>
|
<addaction name="menuView"/>
|
||||||
<addaction name="menuGoTo"/>
|
<addaction name="menuGoTo"/>
|
||||||
<addaction name="menuTools"/>
|
<addaction name="menuTools"/>
|
||||||
<addaction name="menuHelp"/>
|
<addaction name="menuHelp"/>
|
||||||
|
<addaction name="menuDeveloper"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QToolBar" name="mainToolBar">
|
<widget class="QToolBar" name="mainToolBar">
|
||||||
<attribute name="toolBarArea">
|
<attribute name="toolBarArea">
|
||||||
@ -316,6 +324,22 @@
|
|||||||
<string>Send by E-Mail</string>
|
<string>Send by E-Mail</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</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>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<resources>
|
<resources>
|
||||||
|
Reference in New Issue
Block a user