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 #include "textdocumentgenerator.h" 0008 #include "textdocumentgenerator_p.h" 0009 0010 #include <QFile> 0011 #include <QFontDatabase> 0012 #include <QImage> 0013 #include <QMutex> 0014 #include <QPainter> 0015 #include <QPrinter> 0016 #include <QStack> 0017 #include <QTextDocumentWriter> 0018 #include <QTextStream> 0019 #include <QVector> 0020 0021 #include "action.h" 0022 #include "annotations.h" 0023 #include "document_p.h" 0024 #include "page.h" 0025 #include "textpage.h" 0026 0027 #include <cmath> 0028 0029 using namespace Okular; 0030 0031 /** 0032 * Generic Converter Implementation 0033 */ 0034 TextDocumentConverter::TextDocumentConverter() 0035 : QObject(nullptr) 0036 , d_ptr(new TextDocumentConverterPrivate) 0037 { 0038 } 0039 0040 TextDocumentConverter::~TextDocumentConverter() 0041 { 0042 delete d_ptr; 0043 } 0044 0045 QTextDocument *TextDocumentConverter::convert(const QString &) 0046 { 0047 return nullptr; 0048 } 0049 0050 Document::OpenResult TextDocumentConverter::convertWithPassword(const QString &fileName, const QString &) 0051 { 0052 QTextDocument *doc = convert(fileName); 0053 setDocument(doc); 0054 return doc != nullptr ? Document::OpenSuccess : Document::OpenError; 0055 } 0056 0057 QTextDocument *TextDocumentConverter::document() 0058 { 0059 return d_ptr->mDocument; 0060 } 0061 0062 void TextDocumentConverter::setDocument(QTextDocument *document) 0063 { 0064 d_ptr->mDocument = document; 0065 } 0066 0067 DocumentViewport TextDocumentConverter::calculateViewport(QTextDocument *document, const QTextBlock &block) 0068 { 0069 return TextDocumentUtils::calculateViewport(document, block); 0070 } 0071 0072 TextDocumentGenerator *TextDocumentConverter::generator() const 0073 { 0074 return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr; 0075 } 0076 0077 /** 0078 * Generic Generator Implementation 0079 */ 0080 Okular::TextPage *TextDocumentGeneratorPrivate::createTextPage(int pageNumber) const 0081 { 0082 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0083 Q_Q(const TextDocumentGenerator); 0084 #endif 0085 0086 Okular::TextPage *textPage = new Okular::TextPage; 0087 0088 int start, end; 0089 0090 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0091 q->userMutex()->lock(); 0092 #endif 0093 TextDocumentUtils::calculatePositions(mDocument, pageNumber, start, end); 0094 0095 { 0096 QTextCursor cursor(mDocument); 0097 for (int i = start; i < end - 1; ++i) { 0098 cursor.setPosition(i); 0099 cursor.setPosition(i + 1, QTextCursor::KeepAnchor); 0100 0101 QString text = cursor.selectedText(); 0102 if (text.length() == 1) { 0103 QRectF rect; 0104 TextDocumentUtils::calculateBoundingRect(mDocument, i, i + 1, rect, pageNumber); 0105 if (pageNumber == -1) { 0106 text = QStringLiteral("\n"); 0107 } 0108 0109 textPage->append(text, Okular::NormalizedRect(rect.left(), rect.top(), rect.right(), rect.bottom())); 0110 } 0111 } 0112 } 0113 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0114 q->userMutex()->unlock(); 0115 #endif 0116 0117 return textPage; 0118 } 0119 0120 void TextDocumentGeneratorPrivate::addAction(Action *action, int cursorBegin, int cursorEnd) 0121 { 0122 if (!action) { 0123 return; 0124 } 0125 0126 LinkPosition position; 0127 position.link = action; 0128 position.startPosition = cursorBegin; 0129 position.endPosition = cursorEnd; 0130 0131 mLinkPositions.append(position); 0132 } 0133 0134 void TextDocumentGeneratorPrivate::addAnnotation(Annotation *annotation, int cursorBegin, int cursorEnd) 0135 { 0136 if (!annotation) { 0137 return; 0138 } 0139 0140 annotation->setFlags(annotation->flags() | Okular::Annotation::External); 0141 0142 AnnotationPosition position; 0143 position.annotation = annotation; 0144 position.startPosition = cursorBegin; 0145 position.endPosition = cursorEnd; 0146 0147 mAnnotationPositions.append(position); 0148 } 0149 0150 void TextDocumentGeneratorPrivate::addTitle(int level, const QString &title, const QTextBlock &block) 0151 { 0152 TitlePosition position; 0153 position.level = level; 0154 position.title = title; 0155 position.block = block; 0156 0157 mTitlePositions.append(position); 0158 } 0159 0160 void TextDocumentGeneratorPrivate::addMetaData(DocumentInfo::Key key, const QString &value) 0161 { 0162 mDocumentInfo.set(key, value); 0163 } 0164 0165 QList<TextDocumentGeneratorPrivate::LinkInfo> TextDocumentGeneratorPrivate::generateLinkInfos() const 0166 { 0167 QList<LinkInfo> result; 0168 0169 for (int i = 0; i < mLinkPositions.count(); ++i) { 0170 const LinkPosition &linkPosition = mLinkPositions[i]; 0171 0172 const QVector<QRectF> rects = TextDocumentUtils::calculateBoundingRects(mDocument, linkPosition.startPosition, linkPosition.endPosition); 0173 0174 for (int i = 0; i < rects.count(); ++i) { 0175 const QRectF &rect = rects[i]; 0176 0177 LinkInfo info; 0178 info.link = linkPosition.link; 0179 info.ownsLink = i == 0; 0180 info.page = std::floor(rect.y()); 0181 info.boundingRect = QRectF(rect.x(), rect.y() - info.page, rect.width(), rect.height()); 0182 result.append(info); 0183 } 0184 } 0185 0186 return result; 0187 } 0188 0189 QList<TextDocumentGeneratorPrivate::AnnotationInfo> TextDocumentGeneratorPrivate::generateAnnotationInfos() const 0190 { 0191 QList<AnnotationInfo> result; 0192 0193 for (int i = 0; i < mAnnotationPositions.count(); ++i) { 0194 const AnnotationPosition &annotationPosition = mAnnotationPositions[i]; 0195 0196 AnnotationInfo info; 0197 info.annotation = annotationPosition.annotation; 0198 0199 TextDocumentUtils::calculateBoundingRect(mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page); 0200 0201 if (info.page >= 0) { 0202 result.append(info); 0203 } 0204 } 0205 0206 return result; 0207 } 0208 0209 void TextDocumentGeneratorPrivate::generateTitleInfos() 0210 { 0211 QStack<QPair<int, QDomNode>> parentNodeStack; 0212 0213 QDomNode parentNode = mDocumentSynopsis; 0214 0215 parentNodeStack.push(qMakePair(0, parentNode)); 0216 0217 for (int i = 0; i < mTitlePositions.count(); ++i) { 0218 const TitlePosition &position = mTitlePositions[i]; 0219 0220 Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport(mDocument, position.block); 0221 0222 QDomElement item = mDocumentSynopsis.createElement(position.title); 0223 item.setAttribute(QStringLiteral("Viewport"), viewport.toString()); 0224 0225 int headingLevel = position.level; 0226 0227 // we need a parent, which has to be at a higher heading level than this heading level 0228 // so we just work through the stack 0229 while (!parentNodeStack.isEmpty()) { 0230 int parentLevel = parentNodeStack.top().first; 0231 if (parentLevel < headingLevel) { 0232 // this is OK as a parent 0233 parentNode = parentNodeStack.top().second; 0234 break; 0235 } else { 0236 // we'll need to be further into the stack 0237 parentNodeStack.pop(); 0238 } 0239 } 0240 parentNode.appendChild(item); 0241 parentNodeStack.push(qMakePair(headingLevel, QDomNode(item))); 0242 } 0243 } 0244 0245 void TextDocumentGeneratorPrivate::initializeGenerator() 0246 { 0247 Q_Q(TextDocumentGenerator); 0248 0249 mConverter->d_ptr->mParent = q->d_func(); 0250 0251 if (mGeneralSettings) { 0252 mFont = mGeneralSettings->font(); 0253 } 0254 0255 q->setFeature(Generator::TextExtraction); 0256 q->setFeature(Generator::PrintNative); 0257 q->setFeature(Generator::PrintToFile); 0258 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0259 q->setFeature(Generator::Threaded); 0260 #endif 0261 0262 QObject::connect(mConverter, &TextDocumentConverter::addAction, q, [this](Action *a, int cb, int ce) { addAction(a, cb, ce); }); 0263 QObject::connect(mConverter, &TextDocumentConverter::addAnnotation, q, [this](Annotation *a, int cb, int ce) { addAnnotation(a, cb, ce); }); 0264 QObject::connect(mConverter, &TextDocumentConverter::addTitle, q, [this](int l, const QString &t, const QTextBlock &b) { addTitle(l, t, b); }); 0265 QObject::connect(mConverter, &TextDocumentConverter::addMetaData, q, [this](DocumentInfo::Key k, const QString &v) { addMetaData(k, v); }); 0266 0267 QObject::connect(mConverter, &TextDocumentConverter::error, q, &Generator::error); 0268 QObject::connect(mConverter, &TextDocumentConverter::warning, q, &Generator::warning); 0269 QObject::connect(mConverter, &TextDocumentConverter::notice, q, &Generator::notice); 0270 } 0271 0272 TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args) 0273 : Okular::Generator(*new TextDocumentGeneratorPrivate(converter), parent, args) 0274 { 0275 Q_D(TextDocumentGenerator); 0276 d->mGeneralSettings = new TextDocumentSettings(configName, this); 0277 0278 d->initializeGenerator(); 0279 } 0280 0281 TextDocumentGenerator::~TextDocumentGenerator() 0282 { 0283 } 0284 0285 Document::OpenResult TextDocumentGenerator::loadDocumentWithPassword(const QString &fileName, QVector<Okular::Page *> &pagesVector, const QString &password) 0286 { 0287 Q_D(TextDocumentGenerator); 0288 const Document::OpenResult openResult = d->mConverter->convertWithPassword(fileName, password); 0289 0290 if (openResult != Document::OpenSuccess) { 0291 d->mDocument = nullptr; 0292 0293 // loading failed, cleanup all the stuff eventually gathered from the converter 0294 d->mTitlePositions.clear(); 0295 for (const TextDocumentGeneratorPrivate::LinkPosition &linkPos : std::as_const(d->mLinkPositions)) { 0296 delete linkPos.link; 0297 } 0298 d->mLinkPositions.clear(); 0299 for (const TextDocumentGeneratorPrivate::AnnotationPosition &annPos : std::as_const(d->mAnnotationPositions)) { 0300 delete annPos.annotation; 0301 } 0302 d->mAnnotationPositions.clear(); 0303 0304 return openResult; 0305 } 0306 d->mDocument = d->mConverter->document(); 0307 0308 d->generateTitleInfos(); 0309 const QList<TextDocumentGeneratorPrivate::LinkInfo> linkInfos = d->generateLinkInfos(); 0310 const QList<TextDocumentGeneratorPrivate::AnnotationInfo> annotationInfos = d->generateAnnotationInfos(); 0311 0312 pagesVector.resize(d->mDocument->pageCount()); 0313 0314 const QSize size = d->mDocument->pageSize().toSize(); 0315 0316 QVector<QList<Okular::ObjectRect *>> objects(d->mDocument->pageCount()); 0317 for (const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos) { 0318 // in case that the converter report bogus link info data, do not assert here 0319 if (info.page < 0 || info.page >= objects.count()) { 0320 continue; 0321 } 0322 0323 const QRectF rect = info.boundingRect; 0324 if (info.ownsLink) { 0325 objects[info.page].append(new Okular::ObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link)); 0326 } else { 0327 objects[info.page].append(new Okular::NonOwningObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link)); 0328 } 0329 } 0330 0331 QVector<QList<Okular::Annotation *>> annots(d->mDocument->pageCount()); 0332 for (const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos) { 0333 annots[info.page].append(info.annotation); 0334 } 0335 0336 for (int i = 0; i < d->mDocument->pageCount(); ++i) { 0337 Okular::Page *page = new Okular::Page(i, size.width(), size.height(), Okular::Rotation0); 0338 pagesVector[i] = page; 0339 0340 if (!objects.at(i).isEmpty()) { 0341 page->setObjectRects(objects.at(i)); 0342 } 0343 QList<Okular::Annotation *>::ConstIterator annIt = annots.at(i).begin(), annEnd = annots.at(i).end(); 0344 for (; annIt != annEnd; ++annIt) { 0345 page->addAnnotation(*annIt); 0346 } 0347 } 0348 0349 return openResult; 0350 } 0351 0352 bool TextDocumentGenerator::doCloseDocument() 0353 { 0354 Q_D(TextDocumentGenerator); 0355 delete d->mDocument; 0356 d->mDocument = nullptr; 0357 0358 d->mTitlePositions.clear(); 0359 d->mLinkPositions.clear(); 0360 d->mAnnotationPositions.clear(); 0361 // do not use clear() for the following two, otherwise they change type 0362 d->mDocumentInfo = Okular::DocumentInfo(); 0363 d->mDocumentSynopsis = Okular::DocumentSynopsis(); 0364 0365 return true; 0366 } 0367 0368 bool TextDocumentGenerator::canGeneratePixmap() const 0369 { 0370 return Generator::canGeneratePixmap(); 0371 } 0372 0373 void TextDocumentGenerator::generatePixmap(Okular::PixmapRequest *request) 0374 { 0375 Generator::generatePixmap(request); 0376 } 0377 0378 QImage TextDocumentGeneratorPrivate::image(PixmapRequest *request) 0379 { 0380 if (!mDocument) { 0381 return QImage(); 0382 } 0383 0384 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0385 Q_Q(TextDocumentGenerator); 0386 #endif 0387 0388 QImage image(request->width(), request->height(), QImage::Format_ARGB32); 0389 image.fill(Qt::white); 0390 0391 QPainter p; 0392 p.begin(&image); 0393 0394 qreal width = request->width(); 0395 qreal height = request->height(); 0396 0397 const QSize size = mDocument->pageSize().toSize(); 0398 0399 p.scale(width / (qreal)size.width(), height / (qreal)size.height()); 0400 0401 QRect rect; 0402 rect = QRect(0, request->pageNumber() * size.height(), size.width(), size.height()); 0403 p.translate(QPoint(0, request->pageNumber() * size.height() * -1)); 0404 p.setClipRect(rect); 0405 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0406 q->userMutex()->lock(); 0407 #endif 0408 QAbstractTextDocumentLayout::PaintContext context; 0409 context.palette.setColor(QPalette::Text, Qt::black); 0410 // FIXME Fix Qt, this doesn't work, we have horrible hacks 0411 // in the generators that return html, remove that code 0412 // if Qt ever gets fixed 0413 // context.palette.setColor( QPalette::Link, Qt::blue ); 0414 context.clip = rect; 0415 mDocument->setDefaultFont(mFont); 0416 mDocument->documentLayout()->draw(&p, context); 0417 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING 0418 q->userMutex()->unlock(); 0419 #endif 0420 p.end(); 0421 0422 return image; 0423 } 0424 0425 Okular::TextPage *TextDocumentGenerator::textPage(Okular::TextRequest *request) 0426 { 0427 Q_D(TextDocumentGenerator); 0428 return d->createTextPage(request->page()->number()); 0429 } 0430 0431 Document::PrintError TextDocumentGenerator::print(QPrinter &printer) 0432 { 0433 Q_D(TextDocumentGenerator); 0434 if (!d->mDocument) { 0435 return Document::UnknownPrintError; 0436 } 0437 0438 d->mDocument->print(&printer); 0439 0440 return Document::NoPrintError; 0441 } 0442 0443 Okular::DocumentInfo TextDocumentGenerator::generateDocumentInfo(const QSet<DocumentInfo::Key> & /*keys*/) const 0444 { 0445 Q_D(const TextDocumentGenerator); 0446 return d->mDocumentInfo; 0447 } 0448 0449 const Okular::DocumentSynopsis *TextDocumentGenerator::generateDocumentSynopsis() 0450 { 0451 Q_D(TextDocumentGenerator); 0452 if (!d->mDocumentSynopsis.hasChildNodes()) { 0453 return nullptr; 0454 } else { 0455 return &d->mDocumentSynopsis; 0456 } 0457 } 0458 0459 QVariant TextDocumentGeneratorPrivate::metaData(const QString &key, const QVariant &option) const 0460 { 0461 Q_UNUSED(option) 0462 if (key == QLatin1String("DocumentTitle")) { 0463 return mDocumentInfo.get(DocumentInfo::Title); 0464 } 0465 return QVariant(); 0466 } 0467 0468 Okular::ExportFormat::List TextDocumentGenerator::exportFormats() const 0469 { 0470 static Okular::ExportFormat::List formats; 0471 if (formats.isEmpty()) { 0472 formats.append(Okular::ExportFormat::standardFormat(Okular::ExportFormat::PlainText)); 0473 formats.append(Okular::ExportFormat::standardFormat(Okular::ExportFormat::PDF)); 0474 if (QTextDocumentWriter::supportedDocumentFormats().contains("ODF")) { 0475 formats.append(Okular::ExportFormat::standardFormat(Okular::ExportFormat::OpenDocumentText)); 0476 } 0477 if (QTextDocumentWriter::supportedDocumentFormats().contains("HTML")) { 0478 formats.append(Okular::ExportFormat::standardFormat(Okular::ExportFormat::HTML)); 0479 } 0480 } 0481 0482 return formats; 0483 } 0484 0485 bool TextDocumentGenerator::exportTo(const QString &fileName, const Okular::ExportFormat &format) 0486 { 0487 Q_D(TextDocumentGenerator); 0488 if (!d->mDocument) { 0489 return false; 0490 } 0491 0492 if (format.mimeType().name() == QLatin1String("application/pdf")) { 0493 QFile file(fileName); 0494 if (!file.open(QIODevice::WriteOnly)) { 0495 return false; 0496 } 0497 0498 QPrinter printer(QPrinter::HighResolution); 0499 printer.setOutputFormat(QPrinter::PdfFormat); 0500 printer.setOutputFileName(fileName); 0501 d->mDocument->print(&printer); 0502 0503 return true; 0504 } else if (format.mimeType().name() == QLatin1String("text/plain")) { 0505 QFile file(fileName); 0506 if (!file.open(QIODevice::WriteOnly)) { 0507 return false; 0508 } 0509 0510 QTextStream out(&file); 0511 out << d->mDocument->toPlainText(); 0512 0513 return true; 0514 } else if (format.mimeType().name() == QLatin1String("application/vnd.oasis.opendocument.text")) { 0515 QTextDocumentWriter odfWriter(fileName, "odf"); 0516 0517 return odfWriter.write(d->mDocument); 0518 } else if (format.mimeType().name() == QLatin1String("text/html")) { 0519 QTextDocumentWriter odfWriter(fileName, "html"); 0520 0521 return odfWriter.write(d->mDocument); 0522 } 0523 return false; 0524 } 0525 0526 bool TextDocumentGenerator::reparseConfig() 0527 { 0528 Q_D(TextDocumentGenerator); 0529 const QFont newFont = d->mGeneralSettings->font(); 0530 0531 if (newFont != d->mFont) { 0532 d->mFont = newFont; 0533 return true; 0534 } 0535 0536 return false; 0537 } 0538 0539 void TextDocumentGenerator::addPages(KConfigDialog * /*dlg*/) 0540 { 0541 qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator"; 0542 return; 0543 } 0544 0545 TextDocumentSettings *TextDocumentGenerator::generalSettings() 0546 { 0547 Q_D(TextDocumentGenerator); 0548 0549 return d->mGeneralSettings; 0550 } 0551 0552 TextDocumentConverter *TextDocumentGenerator::converter() 0553 { 0554 Q_D(TextDocumentGenerator); 0555 0556 return d->mConverter; 0557 } 0558 0559 void TextDocumentGenerator::setTextDocument(QTextDocument *textDocument) 0560 { 0561 Q_D(TextDocumentGenerator); 0562 0563 d->mDocument = textDocument; 0564 0565 for (Page *p : std::as_const(d->m_document->m_pagesVector)) { 0566 p->setTextPage(nullptr); 0567 } 0568 }