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 <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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user