Okular

textdocumentgenerator.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "textdocumentgenerator.h"
8#include "textdocumentgenerator_p.h"
9
10#include <QFile>
11#include <QFontDatabase>
12#include <QImage>
13#include <QMutex>
14#include <QPainter>
15#include <QPrinter>
16#include <QStack>
17#include <QTextDocumentWriter>
18#include <QTextStream>
19#include <QVector>
20
21#include "action.h"
22#include "annotations.h"
23#include "document_p.h"
24#include "page.h"
25#include "textpage.h"
26
27#include <cmath>
28
29using namespace Okular;
30
31/**
32 * Generic Converter Implementation
33 */
34TextDocumentConverter::TextDocumentConverter()
35 : QObject(nullptr)
36 , d_ptr(new TextDocumentConverterPrivate)
37{
38}
39
40TextDocumentConverter::~TextDocumentConverter()
41{
42 delete d_ptr;
43}
44
45QTextDocument *TextDocumentConverter::convert(const QString &)
46{
47 return nullptr;
48}
49
50Document::OpenResult TextDocumentConverter::convertWithPassword(const QString &fileName, const QString &)
51{
52 QTextDocument *doc = convert(fileName);
53 setDocument(doc);
54 return doc != nullptr ? Document::OpenSuccess : Document::OpenError;
55}
56
57QTextDocument *TextDocumentConverter::document()
58{
59 return d_ptr->mDocument;
60}
61
62void TextDocumentConverter::setDocument(QTextDocument *document)
63{
64 d_ptr->mDocument = document;
65}
66
67DocumentViewport TextDocumentConverter::calculateViewport(QTextDocument *document, const QTextBlock &block)
68{
69 return TextDocumentUtils::calculateViewport(document, block);
70}
71
72TextDocumentGenerator *TextDocumentConverter::generator() const
73{
74 return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr;
75}
76
77/**
78 * Generic Generator Implementation
79 */
80Okular::TextPage *TextDocumentGeneratorPrivate::createTextPage(int pageNumber) const
81{
82#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
83 Q_Q(const TextDocumentGenerator);
84#endif
85
86 Okular::TextPage *textPage = new Okular::TextPage;
87
88 int start, end;
89
90#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
91 q->userMutex()->lock();
92#endif
93 TextDocumentUtils::calculatePositions(mDocument, pageNumber, start, end);
94
95 {
96 QTextCursor cursor(mDocument);
97 for (int i = start; i < end - 1; ++i) {
98 cursor.setPosition(i);
99 cursor.setPosition(i + 1, QTextCursor::KeepAnchor);
100
101 QString text = cursor.selectedText();
102 if (text.length() == 1) {
103 QRectF rect;
104 TextDocumentUtils::calculateBoundingRect(mDocument, i, i + 1, rect, pageNumber);
105 if (pageNumber == -1) {
106 text = QStringLiteral("\n");
107 }
108
109 textPage->append(text, Okular::NormalizedRect(rect.left(), rect.top(), rect.right(), rect.bottom()));
110 }
111 }
112 }
113#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
114 q->userMutex()->unlock();
115#endif
116
117 return textPage;
118}
119
120void TextDocumentGeneratorPrivate::addAction(Action *action, int cursorBegin, int cursorEnd)
121{
122 if (!action) {
123 return;
124 }
125
126 LinkPosition position;
127 position.link = action;
128 position.startPosition = cursorBegin;
129 position.endPosition = cursorEnd;
130
131 mLinkPositions.append(position);
132}
133
134void TextDocumentGeneratorPrivate::addAnnotation(Annotation *annotation, int cursorBegin, int cursorEnd)
135{
136 if (!annotation) {
137 return;
138 }
139
140 annotation->setFlags(annotation->flags() | Okular::Annotation::External);
141
142 AnnotationPosition position;
143 position.annotation = annotation;
144 position.startPosition = cursorBegin;
145 position.endPosition = cursorEnd;
146
147 mAnnotationPositions.append(position);
148}
149
150void TextDocumentGeneratorPrivate::addTitle(int level, const QString &title, const QTextBlock &block)
151{
152 TitlePosition position;
153 position.level = level;
154 position.title = title;
155 position.block = block;
156
157 mTitlePositions.append(position);
158}
159
160void TextDocumentGeneratorPrivate::addMetaData(DocumentInfo::Key key, const QString &value)
161{
162 mDocumentInfo.set(key, value);
163}
164
165QList<TextDocumentGeneratorPrivate::LinkInfo> TextDocumentGeneratorPrivate::generateLinkInfos() const
166{
167 QList<LinkInfo> result;
168
169 for (const LinkPosition &linkPosition : mLinkPositions) {
170 const QVector<QRectF> rects = TextDocumentUtils::calculateBoundingRects(mDocument, linkPosition.startPosition, linkPosition.endPosition);
171
172 for (int i = 0; i < rects.count(); ++i) {
173 const QRectF &rect = rects[i];
174
175 LinkInfo info;
176 info.link = linkPosition.link;
177 info.ownsLink = i == 0;
178 info.page = std::floor(rect.y());
179 info.boundingRect = QRectF(rect.x(), rect.y() - info.page, rect.width(), rect.height());
180 result.append(info);
181 }
182 }
183
184 return result;
185}
186
187QList<TextDocumentGeneratorPrivate::AnnotationInfo> TextDocumentGeneratorPrivate::generateAnnotationInfos() const
188{
190
191 for (int i = 0; i < mAnnotationPositions.count(); ++i) {
192 const AnnotationPosition &annotationPosition = mAnnotationPositions[i];
193
194 AnnotationInfo info;
195 info.annotation = annotationPosition.annotation;
196
197 TextDocumentUtils::calculateBoundingRect(mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page);
198
199 if (info.page >= 0) {
200 result.append(info);
201 }
202 }
203
204 return result;
205}
206
207void TextDocumentGeneratorPrivate::generateTitleInfos()
208{
209 QStack<QPair<int, QDomNode>> parentNodeStack;
210
211 QDomNode parentNode = mDocumentSynopsis;
212
213 parentNodeStack.push(qMakePair(0, parentNode));
214
215 for (int i = 0; i < mTitlePositions.count(); ++i) {
216 const TitlePosition &position = mTitlePositions[i];
217
218 Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport(mDocument, position.block);
219
220 QDomElement item = mDocumentSynopsis.createElement(position.title);
221 item.setAttribute(QStringLiteral("Viewport"), viewport.toString());
222
223 int headingLevel = position.level;
224
225 // we need a parent, which has to be at a higher heading level than this heading level
226 // so we just work through the stack
227 while (!parentNodeStack.isEmpty()) {
228 int parentLevel = parentNodeStack.top().first;
229 if (parentLevel < headingLevel) {
230 // this is OK as a parent
231 parentNode = parentNodeStack.top().second;
232 break;
233 } else {
234 // we'll need to be further into the stack
235 parentNodeStack.pop();
236 }
237 }
238 parentNode.appendChild(item);
239 parentNodeStack.push(qMakePair(headingLevel, QDomNode(item)));
240 }
241}
242
243void TextDocumentGeneratorPrivate::initializeGenerator()
244{
246
247 mConverter->d_ptr->mParent = q->d_func();
248
249 if (mGeneralSettings) {
250 mFont = mGeneralSettings->font();
251 }
252
253 q->setFeature(Generator::TextExtraction);
254 q->setFeature(Generator::PrintNative);
255 q->setFeature(Generator::PrintToFile);
256#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
257 q->setFeature(Generator::Threaded);
258#endif
259
260 QObject::connect(mConverter, &TextDocumentConverter::addAction, q, [this](Action *a, int cb, int ce) { addAction(a, cb, ce); });
261 QObject::connect(mConverter, &TextDocumentConverter::addAnnotation, q, [this](Annotation *a, int cb, int ce) { addAnnotation(a, cb, ce); });
262 QObject::connect(mConverter, &TextDocumentConverter::addTitle, q, [this](int l, const QString &t, const QTextBlock &b) { addTitle(l, t, b); });
263 QObject::connect(mConverter, &TextDocumentConverter::addMetaData, q, [this](DocumentInfo::Key k, const QString &v) { addMetaData(k, v); });
264
265 QObject::connect(mConverter, &TextDocumentConverter::error, q, &Generator::error);
266 QObject::connect(mConverter, &TextDocumentConverter::warning, q, &Generator::warning);
267 QObject::connect(mConverter, &TextDocumentConverter::notice, q, &Generator::notice);
268}
269
270TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args)
271 : Okular::Generator(*new TextDocumentGeneratorPrivate(converter), parent, args)
272{
274 d->mGeneralSettings = new TextDocumentSettings(configName, this);
275
276 d->initializeGenerator();
277}
278
279TextDocumentGenerator::~TextDocumentGenerator()
280{
281}
282
284{
286 const Document::OpenResult openResult = d->mConverter->convertWithPassword(fileName, password);
287
288 if (openResult != Document::OpenSuccess) {
289 d->mDocument = nullptr;
290
291 // loading failed, cleanup all the stuff eventually gathered from the converter
292 d->mTitlePositions.clear();
293 for (const TextDocumentGeneratorPrivate::LinkPosition &linkPos : std::as_const(d->mLinkPositions)) {
294 delete linkPos.link;
295 }
296 d->mLinkPositions.clear();
297 for (const TextDocumentGeneratorPrivate::AnnotationPosition &annPos : std::as_const(d->mAnnotationPositions)) {
298 delete annPos.annotation;
299 }
300 d->mAnnotationPositions.clear();
301
302 return openResult;
303 }
304 d->mDocument = d->mConverter->document();
305
306 d->generateTitleInfos();
307 const QList<TextDocumentGeneratorPrivate::LinkInfo> linkInfos = d->generateLinkInfos();
308 const QList<TextDocumentGeneratorPrivate::AnnotationInfo> annotationInfos = d->generateAnnotationInfos();
309
310 pagesVector.resize(d->mDocument->pageCount());
311
312 const QSize size = d->mDocument->pageSize().toSize();
313
314 QVector<QList<Okular::ObjectRect *>> objects(d->mDocument->pageCount());
315 for (const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos) {
316 // in case that the converter report bogus link info data, do not assert here
317 if (info.page < 0 || info.page >= objects.count()) {
318 continue;
319 }
320
321 const QRectF rect = info.boundingRect;
322 if (info.ownsLink) {
323 objects[info.page].append(new Okular::ObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link));
324 } else {
325 objects[info.page].append(new Okular::NonOwningObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link));
326 }
327 }
328
329 QVector<QList<Okular::Annotation *>> annots(d->mDocument->pageCount());
330 for (const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos) {
331 annots[info.page].append(info.annotation);
332 }
333
334 for (int i = 0; i < d->mDocument->pageCount(); ++i) {
335 Okular::Page *page = new Okular::Page(i, size.width(), size.height(), Okular::Rotation0);
336 pagesVector[i] = page;
337
338 if (!objects.at(i).isEmpty()) {
339 page->setObjectRects(objects.at(i));
340 }
341 QList<Okular::Annotation *>::ConstIterator annIt = annots.at(i).begin(), annEnd = annots.at(i).end();
342 for (; annIt != annEnd; ++annIt) {
343 page->addAnnotation(*annIt);
344 }
345 }
346
347 return openResult;
348}
349
351{
353 delete d->mDocument;
354 d->mDocument = nullptr;
355
356 d->mTitlePositions.clear();
357 d->mLinkPositions.clear();
358 d->mAnnotationPositions.clear();
359 // do not use clear() for the following two, otherwise they change type
360 d->mDocumentInfo = Okular::DocumentInfo();
361 d->mDocumentSynopsis = Okular::DocumentSynopsis();
362
363 return true;
364}
365
370
375
376QImage TextDocumentGeneratorPrivate::image(PixmapRequest *request)
377{
378 if (!mDocument) {
379 return QImage();
380 }
381
382#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
384#endif
385
386 QImage image(request->width(), request->height(), QImage::Format_ARGB32);
387 image.fill(Qt::white);
388
389 QPainter p;
390 p.begin(&image);
391
392 qreal width = request->width();
393 qreal height = request->height();
394
395 const QSize size = mDocument->pageSize().toSize();
396
397 p.scale(width / (qreal)size.width(), height / (qreal)size.height());
398
399 QRect rect;
400 rect = QRect(0, request->pageNumber() * size.height(), size.width(), size.height());
401 p.translate(QPoint(0, request->pageNumber() * size.height() * -1));
402 p.setClipRect(rect);
403#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
404 q->userMutex()->lock();
405#endif
407 context.palette.setColor(QPalette::Text, Qt::black);
408 // FIXME Fix Qt, this doesn't work, we have horrible hacks
409 // in the generators that return html, remove that code
410 // if Qt ever gets fixed
411 // context.palette.setColor( QPalette::Link, Qt::blue );
412 context.clip = rect;
413 mDocument->setDefaultFont(mFont);
414 mDocument->documentLayout()->draw(&p, context);
415#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
416 q->userMutex()->unlock();
417#endif
418 p.end();
419
420 return image;
421}
422
424{
426 return d->createTextPage(request->page()->number());
427}
428
430{
432 if (!d->mDocument) {
433 return Document::UnknownPrintError;
434 }
435
436 d->mDocument->print(&printer);
437
439}
440
442{
444 return d->mDocumentInfo;
445}
446
448{
450 if (!d->mDocumentSynopsis.hasChildNodes()) {
451 return nullptr;
452 } else {
453 return &d->mDocumentSynopsis;
454 }
455}
456
457QVariant TextDocumentGeneratorPrivate::metaData(const QString &key, const QVariant &option) const
458{
459 Q_UNUSED(option)
460 if (key == QLatin1String("DocumentTitle")) {
461 return mDocumentInfo.get(DocumentInfo::Title);
462 }
463 return QVariant();
464}
465
482
484{
486 if (!d->mDocument) {
487 return false;
488 }
489
490 if (format.mimeType().name() == QLatin1String("application/pdf")) {
491 QFile file(fileName);
492 if (!file.open(QIODevice::WriteOnly)) {
493 return false;
494 }
495
498 printer.setOutputFileName(fileName);
499 d->mDocument->print(&printer);
500
501 return true;
502 } else if (format.mimeType().name() == QLatin1String("text/plain")) {
503 QFile file(fileName);
504 if (!file.open(QIODevice::WriteOnly)) {
505 return false;
506 }
507
508 QTextStream out(&file);
509 out << d->mDocument->toPlainText();
510
511 return true;
512 } else if (format.mimeType().name() == QLatin1String("application/vnd.oasis.opendocument.text")) {
513 QTextDocumentWriter odfWriter(fileName, "odf");
514
515 return odfWriter.write(d->mDocument);
516 } else if (format.mimeType().name() == QLatin1String("text/html")) {
517 QTextDocumentWriter odfWriter(fileName, "html");
518
519 return odfWriter.write(d->mDocument);
520 }
521 return false;
522}
523
525{
527 const QFont newFont = d->mGeneralSettings->font();
528
529 if (newFont != d->mFont) {
530 d->mFont = newFont;
531 return true;
532 }
533
534 return false;
535}
536
538{
539 qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator";
540 return;
541}
542
544{
546
547 return d->mGeneralSettings;
548}
549
550TextDocumentConverter *TextDocumentGenerator::converter()
551{
553
554 return d->mConverter;
555}
556
557void TextDocumentGenerator::setTextDocument(QTextDocument *textDocument)
558{
560
561 d->mDocument = textDocument;
562
563 for (Page *p : std::as_const(d->m_document->m_pagesVector)) {
564 p->setTextPage(nullptr);
565 }
566}
Annotation struct holds properties shared by all annotations.
Definition annotations.h:96
int flags() const
Returns the flags of the annotation.
@ External
Is stored external.
void setFlags(int flags)
Sets the flags of the annotation.
The DocumentInfo structure can be filled in by generators to display metadata about the currently ope...
Definition document.h:76
Key
The list of predefined keys.
Definition document.h:83
@ Title
The title of the document.
Definition document.h:84
A DOM tree that describes the Table of Contents.
Definition document.h:1504
A view on the document.
Definition document.h:1423
QString toString() const
Returns the viewport as xml description.
@ NoPrintError
Printing succeeded.
Definition document.h:817
OpenResult
Describes the result of an open document operation.
Definition document.h:210
Defines an entry for the export menu.
Definition generator.h:79
QMimeType mimeType() const
Returns the mime type of the format.
static ExportFormat standardFormat(StandardExportFormat type)
Builds a standard format for the specified type .
@ PDF
PDF, aka Portable Document Format.
Definition generator.h:150
@ HTML
OpenDocument Text format.
Definition generator.h:152
@ OpenDocumentText
OpenDocument Text format.
Definition generator.h:151
@ PlainText
Plain text.
Definition generator.h:149
[Abstract Class] The information generator.
Definition generator.h:188
void error(const QString &message, int duration)
This signal should be emitted whenever an error occurred in the generator.
virtual void generatePixmap(PixmapRequest *request)
This method can be called to trigger the generation of a new pixmap as described by request.
void notice(const QString &message, int duration)
This signal should be emitted whenever the user should be noticed.
@ Threaded
Whether the Generator supports asynchronous generation of pictures or text pages.
Definition generator.h:202
@ PrintToFile
Whether the Generator supports export to PDF & PS through the Print Dialog.
Definition generator.h:209
@ TextExtraction
Whether the Generator can extract text from the document in the form of TextPage's.
Definition generator.h:203
@ PrintNative
Whether the Generator supports native cross-platform printing (QPainter-based).
Definition generator.h:207
virtual bool canGeneratePixmap() const
This method returns whether the generator is ready to handle a new pixmap request.
void warning(const QString &message, int duration)
This signal should be emitted whenever the user should be warned.
This class is an object rect that doesn't own the given pointer, i.e.
Definition area.h:632
A NormalizedRect is a rectangle which can be defined by two NormalizedPoints.
Definition area.h:189
An area with normalized coordinates that contains a reference to an object.
Definition area.h:458
@ Action
An action.
Definition area.h:464
Collector for all the data belonging to a page.
Definition page.h:48
int number() const
Returns the number of the page in the document.
Definition page.cpp:160
void addAnnotation(Annotation *annotation)
Adds a new annotation to the page.
Definition page.cpp:676
void setObjectRects(const QList< ObjectRect * > &rects)
Sets the list of object rects of the page.
Definition page.cpp:597
Describes a pixmap type request.
Definition generator.h:618
int width() const
Returns the page width of the requested pixmap.
int height() const
Returns the page height of the requested pixmap.
int pageNumber() const
Returns the page number of the request.
QTextDocument-based Generator.
bool reparseConfig() override
By default checks if the default font has changed or not.
bool doCloseDocument() override
This method is called when the document is closed and not used any longer.
const Okular::DocumentSynopsis * generateDocumentSynopsis() override
Returns the 'table of content' object of the document or 0 if no table of content is available.
TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args)
Creates a new generator that uses the specified converter.
TextDocumentSettings * generalSettings()
Config skeleton for TextDocumentSettingsWidget.
Okular::TextPage * textPage(Okular::TextRequest *request) override
Returns the text page for the given request.
Okular::DocumentInfo generateDocumentInfo(const QSet< DocumentInfo::Key > &keys) const override
Returns the general information object of the document.
void addPages(KConfigDialog *dlg) override
Does nothing by default. You need to reimplement it in your generator.
Okular::ExportFormat::List exportFormats() const override
Returns the list of additional supported export formats.
bool canGeneratePixmap() const override
This method returns whether the generator is ready to handle a new pixmap request.
void generatePixmap(Okular::PixmapRequest *request) override
This method can be called to trigger the generation of a new pixmap as described by request.
bool exportTo(const QString &fileName, const Okular::ExportFormat &format) override
This method is called to export the document in the given format and save it under the given fileName...
Document::PrintError print(QPrinter &printer) override
This method is called to print the document to the given printer.
Document::OpenResult loadDocumentWithPassword(const QString &fileName, QVector< Okular::Page * > &pagesVector, const QString &password) override
Loads the document with the given fileName and password and fills the pagesVector with the parsed pag...
Represents the textual information of a Page.
Definition textpage.h:102
void append(const QString &text, const NormalizedRect &area)
Appends the given text with the given area as new TextEntity to the page.
Definition textpage.cpp:185
Describes a text request.
Definition generator.h:754
Page * page() const
Returns a pointer to the page where the pixmap shall be generated for.
Q_SCRIPTABLE Q_NOREPLY void start()
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QStringView level(QStringView ifopt)
global.h
Definition action.h:17
@ Rotation0
Not rotated.
Definition global.h:47
void setAttribute(const QString &name, const QString &value)
QDomNode appendChild(const QDomNode &newChild)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void resize(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool begin(QPaintDevice *device)
bool end()
void scale(qreal sx, qreal sy)
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void translate(const QPoint &offset)
void setOutputFileName(const QString &fileName)
void setOutputFormat(OutputFormat format)
qreal bottom() const const
qreal height() const const
qreal left() const const
qreal right() const const
qreal top() const const
qreal width() const const
qreal x() const const
qreal y() const const
int height() const const
int width() const const
void push(const T &t)
T & top()
qsizetype length() const const
QList< QByteArray > supportedDocumentFormats()
bool write(const QTextDocument *document)
T & get(QVariant &v)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:51:37 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.