File indexing completed on 2024-04-28 04:32:46

0001 /*
0002     SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #ifndef _OKULAR_TEXTDOCUMENTGENERATOR_P_H_
0008 #define _OKULAR_TEXTDOCUMENTGENERATOR_P_H_
0009 
0010 #include <QAbstractTextDocumentLayout>
0011 #include <QTextBlock>
0012 #include <QTextDocument>
0013 
0014 #include "action.h"
0015 #include "debug_p.h"
0016 #include "document.h"
0017 #include "generator_p.h"
0018 #include "textdocumentgenerator.h"
0019 
0020 namespace Okular
0021 {
0022 namespace TextDocumentUtils
0023 {
0024 static void calculateBoundingRect(QTextDocument *document, int startPosition, int endPosition, QRectF &rect, int &page)
0025 {
0026     const QSizeF pageSize = document->pageSize();
0027 
0028     const QTextBlock startBlock = document->findBlock(startPosition);
0029     const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect(startBlock);
0030 
0031     const QTextBlock endBlock = document->findBlock(endPosition);
0032     const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect(endBlock);
0033 
0034     const QTextLayout *startLayout = startBlock.layout();
0035     const QTextLayout *endLayout = endBlock.layout();
0036     if (!startLayout || !endLayout) {
0037         qCWarning(OkularCoreDebug) << "Start or end layout not found" << startLayout << endLayout;
0038         page = -1;
0039         return;
0040     }
0041 
0042     const int startPos = startPosition - startBlock.position();
0043     const int endPos = endPosition - endBlock.position();
0044     const QTextLine startLine = startLayout->lineForTextPosition(startPos);
0045     const QTextLine endLine = endLayout->lineForTextPosition(endPos);
0046 
0047     const double x = startBoundingRect.x() + startLine.cursorToX(startPos);
0048     const double y = startBoundingRect.y() + startLine.y();
0049     const double r = endBoundingRect.x() + endLine.cursorToX(endPos);
0050     const double b = endBoundingRect.y() + endLine.y() + endLine.height();
0051 
0052     const int offset = qRound(y) % qRound(pageSize.height());
0053 
0054     if (x > r) { // line break, so return a pseudo character on the start line
0055         rect = QRectF(x / pageSize.width(), offset / pageSize.height(), 3 / pageSize.width(), startLine.height() / pageSize.height());
0056         page = -1;
0057         return;
0058     }
0059 
0060     page = qRound(y) / qRound(pageSize.height());
0061     rect = QRectF(x / pageSize.width(), offset / pageSize.height(), (r - x) / pageSize.width(), (b - y) / pageSize.height());
0062 }
0063 
0064 static QVector<QRectF> calculateBoundingRects(QTextDocument *document, int startPosition, int endPosition)
0065 {
0066     QVector<QRectF> result;
0067 
0068     const QSizeF pageSize = document->pageSize();
0069 
0070     const QTextBlock startBlock = document->findBlock(startPosition);
0071     const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect(startBlock);
0072 
0073     const QTextBlock endBlock = document->findBlock(endPosition);
0074     const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect(endBlock);
0075 
0076     const QTextLayout *startLayout = startBlock.layout();
0077     const QTextLayout *endLayout = endBlock.layout();
0078     if (!startLayout || !endLayout) {
0079         qCWarning(OkularCoreDebug) << "Start or end layout not found" << startLayout << endLayout;
0080         return {};
0081     }
0082 
0083     const int startPos = startPosition - startBlock.position();
0084     const int endPos = endPosition - endBlock.position();
0085     const QTextLine startLine = startLayout->lineForTextPosition(startPos);
0086     const QTextLine endLine = endLayout->lineForTextPosition(endPos);
0087 
0088     // This only works if both start and end layout are the same
0089     if (startLayout == endLayout) {
0090         Q_ASSERT(startBoundingRect == endBoundingRect);
0091         for (int i = startLine.lineNumber(); i < endLine.lineNumber(); ++i) {
0092             const QTextLine line = startLayout->lineAt(i);
0093             // using startPos and endPos is fine, if the pos is out of bounds for that line, it'll return beginning and end of line respectively
0094             const double x = endBoundingRect.x() + line.cursorToX(startPos);
0095             const double y = endBoundingRect.y() + line.y();
0096             const double r = endBoundingRect.x() + line.cursorToX(endPos);
0097             const double b = endBoundingRect.y() + line.y() + endLine.height();
0098 
0099             result.append(QRectF(x / pageSize.width(), y / pageSize.height(), (r - x) / pageSize.width(), (b - y) / pageSize.height()));
0100         }
0101 
0102         // The last line
0103         const double x = endBoundingRect.x() + endLine.cursorToX(startPos);
0104         const double y = endBoundingRect.y() + endLine.y();
0105         const double r = endBoundingRect.x() + endLine.cursorToX(endPos);
0106         const double b = endBoundingRect.y() + endLine.y() + endLine.height();
0107 
0108         result.append(QRectF(x / pageSize.width(), y / pageSize.height(), (r - x) / pageSize.width(), (b - y) / pageSize.height()));
0109     } else {
0110         const double x = startBoundingRect.x() + startLine.cursorToX(startPos);
0111         const double y = startBoundingRect.y() + startLine.y();
0112         const double r = endBoundingRect.x() + endLine.cursorToX(endPos);
0113         const double b = endBoundingRect.y() + endLine.y() + endLine.height();
0114 
0115         result.append(QRectF(x / pageSize.width(), y / pageSize.height(), (r - x) / pageSize.width(), (b - y) / pageSize.height()));
0116     }
0117 
0118     return result;
0119 }
0120 
0121 static void calculatePositions(QTextDocument *document, int page, int &start, int &end)
0122 {
0123     const QAbstractTextDocumentLayout *layout = document->documentLayout();
0124     const QSizeF pageSize = document->pageSize();
0125     const double margin = document->rootFrame()->frameFormat().margin();
0126 
0127     /**
0128      * Take the upper left and lower left corner including the margin
0129      */
0130     start = layout->hitTest(QPointF(margin, (page * pageSize.height()) + margin), Qt::FuzzyHit);
0131     end = layout->hitTest(QPointF(margin, ((page + 1) * pageSize.height()) - margin), Qt::FuzzyHit);
0132 }
0133 
0134 static Okular::DocumentViewport calculateViewport(QTextDocument *document, const QTextBlock &block)
0135 {
0136     const QSizeF pageSize = document->pageSize();
0137     const QRectF rect = document->documentLayout()->blockBoundingRect(block);
0138 
0139     int page = qRound(rect.y()) / qRound(pageSize.height());
0140     int offset = qRound(rect.y()) % qRound(pageSize.height());
0141     if (rect.y() + rect.height() > pageSize.height()) {
0142         page = page + 1;
0143         offset = 0;
0144     }
0145 
0146     Okular::DocumentViewport viewport(page);
0147     viewport.rePos.normalizedX = (double)rect.x() / (double)pageSize.width();
0148     viewport.rePos.normalizedY = (double)offset / (double)pageSize.height();
0149     viewport.rePos.enabled = true;
0150     viewport.rePos.pos = Okular::DocumentViewport::Center;
0151 
0152     return viewport;
0153 }
0154 }
0155 
0156 class TextDocumentConverterPrivate
0157 {
0158 public:
0159     TextDocumentConverterPrivate()
0160         : mParent(nullptr)
0161     {
0162     }
0163 
0164     TextDocumentGeneratorPrivate *mParent;
0165     QTextDocument *mDocument;
0166 };
0167 
0168 class TextDocumentGeneratorPrivate : public GeneratorPrivate
0169 {
0170     friend class TextDocumentConverter;
0171 
0172 public:
0173     explicit TextDocumentGeneratorPrivate(TextDocumentConverter *converter)
0174         : mConverter(converter)
0175         , mDocument(nullptr)
0176         , mGeneralSettings(nullptr)
0177     {
0178     }
0179 
0180     ~TextDocumentGeneratorPrivate() override
0181     {
0182         delete mConverter;
0183         delete mDocument;
0184     }
0185 
0186     void initializeGenerator();
0187 
0188     struct LinkInfo {
0189         int page;
0190         QRectF boundingRect;
0191         Action *link;
0192         bool ownsLink;
0193     };
0194 
0195     struct AnnotationInfo {
0196         int page;
0197         QRectF boundingRect;
0198         Annotation *annotation;
0199     };
0200 
0201     Q_DECLARE_PUBLIC(TextDocumentGenerator)
0202 
0203     /* reimp */ QVariant metaData(const QString &key, const QVariant &option) const override;
0204     /* reimp */ QImage image(PixmapRequest *) override;
0205 
0206     void calculateBoundingRect(int startPosition, int endPosition, QRectF &rect, int &page) const;
0207     void calculatePositions(int page, int &start, int &end) const;
0208     Okular::TextPage *createTextPage(int) const;
0209 
0210     void addAction(Action *action, int cursorBegin, int cursorEnd);
0211     void addAnnotation(Annotation *annotation, int cursorBegin, int cursorEnd);
0212     void addTitle(int level, const QString &title, const QTextBlock &block);
0213     void addMetaData(const QString &key, const QString &value, const QString &title);
0214     void addMetaData(DocumentInfo::Key, const QString &value);
0215 
0216     QList<LinkInfo> generateLinkInfos() const;
0217     QList<AnnotationInfo> generateAnnotationInfos() const;
0218     void generateTitleInfos();
0219 
0220     TextDocumentConverter *mConverter;
0221 
0222     QTextDocument *mDocument;
0223     Okular::DocumentInfo mDocumentInfo;
0224     Okular::DocumentSynopsis mDocumentSynopsis;
0225 
0226     struct TitlePosition {
0227         int level;
0228         QString title;
0229         QTextBlock block;
0230     };
0231     QList<TitlePosition> mTitlePositions;
0232 
0233     struct LinkPosition {
0234         int startPosition;
0235         int endPosition;
0236         Action *link;
0237     };
0238     QList<LinkPosition> mLinkPositions;
0239 
0240     struct AnnotationPosition {
0241         int startPosition;
0242         int endPosition;
0243         Annotation *annotation;
0244     };
0245     QList<AnnotationPosition> mAnnotationPositions;
0246 
0247     TextDocumentSettings *mGeneralSettings;
0248 
0249     QFont mFont;
0250 };
0251 
0252 }
0253 
0254 #endif