mirror of https://github.com/JakubMelka/PDF4QT.git
Bugfixing of text layout
This commit is contained in:
parent
0c97e21f54
commit
afbf37d068
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue