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 (int i = 0; i < mLinkPositions.count(); ++i) {
170 const LinkPosition &linkPosition = mLinkPositions[i];
171
172 const QVector<QRectF> rects = TextDocumentUtils::calculateBoundingRects(mDocument, linkPosition.startPosition, linkPosition.endPosition);
173
174 for (int i = 0; i < rects.count(); ++i) {
175 const QRectF &rect = rects[i];
176
177 LinkInfo info;
178 info.link = linkPosition.link;
179 info.ownsLink = i == 0;
180 info.page = std::floor(rect.y());
181 info.boundingRect = QRectF(rect.x(), rect.y() - info.page, rect.width(), rect.height());
182 result.append(info);
183 }
184 }
185
186 return result;
187}
188
189QList<TextDocumentGeneratorPrivate::AnnotationInfo> TextDocumentGeneratorPrivate::generateAnnotationInfos() const
190{
192
193 for (int i = 0; i < mAnnotationPositions.count(); ++i) {
194 const AnnotationPosition &annotationPosition = mAnnotationPositions[i];
195
196 AnnotationInfo info;
197 info.annotation = annotationPosition.annotation;
198
199 TextDocumentUtils::calculateBoundingRect(mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page);
200
201 if (info.page >= 0) {
202 result.append(info);
203 }
204 }
205
206 return result;
207}
208
209void TextDocumentGeneratorPrivate::generateTitleInfos()
210{
211 QStack<QPair<int, QDomNode>> parentNodeStack;
212
213 QDomNode parentNode = mDocumentSynopsis;
214
215 parentNodeStack.push(qMakePair(0, parentNode));
216
217 for (int i = 0; i < mTitlePositions.count(); ++i) {
218 const TitlePosition &position = mTitlePositions[i];
219
220 Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport(mDocument, position.block);
221
222 QDomElement item = mDocumentSynopsis.createElement(position.title);
223 item.setAttribute(QStringLiteral("Viewport"), viewport.toString());
224
225 int headingLevel = position.level;
226
227 // we need a parent, which has to be at a higher heading level than this heading level
228 // so we just work through the stack
229 while (!parentNodeStack.isEmpty()) {
230 int parentLevel = parentNodeStack.top().first;
231 if (parentLevel < headingLevel) {
232 // this is OK as a parent
233 parentNode = parentNodeStack.top().second;
234 break;
235 } else {
236 // we'll need to be further into the stack
237 parentNodeStack.pop();
238 }
239 }
240 parentNode.appendChild(item);
241 parentNodeStack.push(qMakePair(headingLevel, QDomNode(item)));
242 }
243}
244
245void TextDocumentGeneratorPrivate::initializeGenerator()
246{
248
249 mConverter->d_ptr->mParent = q->d_func();
250
251 if (mGeneralSettings) {
252 mFont = mGeneralSettings->font();
253 }
254
255 q->setFeature(Generator::TextExtraction);
256 q->setFeature(Generator::PrintNative);
257 q->setFeature(Generator::PrintToFile);
258#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
259 q->setFeature(Generator::Threaded);
260#endif
261
262 QObject::connect(mConverter, &TextDocumentConverter::addAction, q, [this](Action *a, int cb, int ce) { addAction(a, cb, ce); });
263 QObject::connect(mConverter, &TextDocumentConverter::addAnnotation, q, [this](Annotation *a, int cb, int ce) { addAnnotation(a, cb, ce); });
264 QObject::connect(mConverter, &TextDocumentConverter::addTitle, q, [this](int l, const QString &t, const QTextBlock &b) { addTitle(l, t, b); });
265 QObject::connect(mConverter, &TextDocumentConverter::addMetaData, q, [this](DocumentInfo::Key k, const QString &v) { addMetaData(k, v); });
266
267 QObject::connect(mConverter, &TextDocumentConverter::error, q, &Generator::error);
268 QObject::connect(mConverter, &TextDocumentConverter::warning, q, &Generator::warning);
269 QObject::connect(mConverter, &TextDocumentConverter::notice, q, &Generator::notice);
270}
271
272TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args)
273 : Okular::Generator(*new TextDocumentGeneratorPrivate(converter), parent, args)
274{
276 d->mGeneralSettings = new TextDocumentSettings(configName, this);
277
278 d->initializeGenerator();
279}
280
281TextDocumentGenerator::~TextDocumentGenerator()
282{
283}
284
286{
288 const Document::OpenResult openResult = d->mConverter->convertWithPassword(fileName, password);
289
290 if (openResult != Document::OpenSuccess) {
291 d->mDocument = nullptr;
292
293 // loading failed, cleanup all the stuff eventually gathered from the converter
294 d->mTitlePositions.clear();
295 for (const TextDocumentGeneratorPrivate::LinkPosition &linkPos : std::as_const(d->mLinkPositions)) {
296 delete linkPos.link;
297 }
298 d->mLinkPositions.clear();
299 for (const TextDocumentGeneratorPrivate::AnnotationPosition &annPos : std::as_const(d->mAnnotationPositions)) {
300 delete annPos.annotation;
301 }
302 d->mAnnotationPositions.clear();
303
304 return openResult;
305 }
306 d->mDocument = d->mConverter->document();
307
308 d->generateTitleInfos();
309 const QList<TextDocumentGeneratorPrivate::LinkInfo> linkInfos = d->generateLinkInfos();
310 const QList<TextDocumentGeneratorPrivate::AnnotationInfo> annotationInfos = d->generateAnnotationInfos();
311
312 pagesVector.resize(d->mDocument->pageCount());
313
314 const QSize size = d->mDocument->pageSize().toSize();
315
316 QVector<QList<Okular::ObjectRect *>> objects(d->mDocument->pageCount());
317 for (const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos) {
318 // in case that the converter report bogus link info data, do not assert here
319 if (info.page < 0 || info.page >= objects.count()) {
320 continue;
321 }
322
323 const QRectF rect = info.boundingRect;
324 if (info.ownsLink) {
325 objects[info.page].append(new Okular::ObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link));
326 } else {
327 objects[info.page].append(new Okular::NonOwningObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link));
328 }
329 }
330
331 QVector<QList<Okular::Annotation *>> annots(d->mDocument->pageCount());
332 for (const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos) {
333 annots[info.page].append(info.annotation);
334 }
335
336 for (int i = 0; i < d->mDocument->pageCount(); ++i) {
337 Okular::Page *page = new Okular::Page(i, size.width(), size.height(), Okular::Rotation0);
338 pagesVector[i] = page;
339
340 if (!objects.at(i).isEmpty()) {
341 page->setObjectRects(objects.at(i));
342 }
343 QList<Okular::Annotation *>::ConstIterator annIt = annots.at(i).begin(), annEnd = annots.at(i).end();
344 for (; annIt != annEnd; ++annIt) {
345 page->addAnnotation(*annIt);
346 }
347 }
348
349 return openResult;
350}
351
353{
355 delete d->mDocument;
356 d->mDocument = nullptr;
357
358 d->mTitlePositions.clear();
359 d->mLinkPositions.clear();
360 d->mAnnotationPositions.clear();
361 // do not use clear() for the following two, otherwise they change type
362 d->mDocumentInfo = Okular::DocumentInfo();
363 d->mDocumentSynopsis = Okular::DocumentSynopsis();
364
365 return true;
366}
367
372
377
378QImage TextDocumentGeneratorPrivate::image(PixmapRequest *request)
379{
380 if (!mDocument) {
381 return QImage();
382 }
383
384#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
386#endif
387
388 QImage image(request->width(), request->height(), QImage::Format_ARGB32);
389 image.fill(Qt::white);
390
391 QPainter p;
392 p.begin(&image);
393
394 qreal width = request->width();
395 qreal height = request->height();
396
397 const QSize size = mDocument->pageSize().toSize();
398
399 p.scale(width / (qreal)size.width(), height / (qreal)size.height());
400
401 QRect rect;
402 rect = QRect(0, request->pageNumber() * size.height(), size.width(), size.height());
403 p.translate(QPoint(0, request->pageNumber() * size.height() * -1));
404 p.setClipRect(rect);
405#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
406 q->userMutex()->lock();
407#endif
409 context.palette.setColor(QPalette::Text, Qt::black);
410 // FIXME Fix Qt, this doesn't work, we have horrible hacks
411 // in the generators that return html, remove that code
412 // if Qt ever gets fixed
413 // context.palette.setColor( QPalette::Link, Qt::blue );
414 context.clip = rect;
415 mDocument->setDefaultFont(mFont);
416 mDocument->documentLayout()->draw(&p, context);
417#ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
418 q->userMutex()->unlock();
419#endif
420 p.end();
421
422 return image;
423}
424
426{
428 return d->createTextPage(request->page()->number());
429}
430
432{
434 if (!d->mDocument) {
435 return Document::UnknownPrintError;
436 }
437
438 d->mDocument->print(&printer);
439
441}
442
444{
446 return d->mDocumentInfo;
447}
448
450{
452 if (!d->mDocumentSynopsis.hasChildNodes()) {
453 return nullptr;
454 } else {
455 return &d->mDocumentSynopsis;
456 }
457}
458
459QVariant TextDocumentGeneratorPrivate::metaData(const QString &key, const QVariant &option) const
460{
461 Q_UNUSED(option)
462 if (key == QLatin1String("DocumentTitle")) {
463 return mDocumentInfo.get(DocumentInfo::Title);
464 }
465 return QVariant();
466}
467
484
486{
488 if (!d->mDocument) {
489 return false;
490 }
491
492 if (format.mimeType().name() == QLatin1String("application/pdf")) {
493 QFile file(fileName);
494 if (!file.open(QIODevice::WriteOnly)) {
495 return false;
496 }
497
500 printer.setOutputFileName(fileName);
501 d->mDocument->print(&printer);
502
503 return true;
504 } else if (format.mimeType().name() == QLatin1String("text/plain")) {
505 QFile file(fileName);
506 if (!file.open(QIODevice::WriteOnly)) {
507 return false;
508 }
509
510 QTextStream out(&file);
511 out << d->mDocument->toPlainText();
512
513 return true;
514 } else if (format.mimeType().name() == QLatin1String("application/vnd.oasis.opendocument.text")) {
515 QTextDocumentWriter odfWriter(fileName, "odf");
516
517 return odfWriter.write(d->mDocument);
518 } else if (format.mimeType().name() == QLatin1String("text/html")) {
519 QTextDocumentWriter odfWriter(fileName, "html");
520
521 return odfWriter.write(d->mDocument);
522 }
523 return false;
524}
525
527{
529 const QFont newFont = d->mGeneralSettings->font();
530
531 if (newFont != d->mFont) {
532 d->mFont = newFont;
533 return true;
534 }
535
536 return false;
537}
538
540{
541 qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator";
542 return;
543}
544
546{
548
549 return d->mGeneralSettings;
550}
551
552TextDocumentConverter *TextDocumentGenerator::converter()
553{
555
556 return d->mConverter;
557}
558
559void TextDocumentGenerator::setTextDocument(QTextDocument *textDocument)
560{
562
563 d->mDocument = textDocument;
564
565 for (Page *p : std::as_const(d->m_document->m_pagesVector)) {
566 p->setTextPage(nullptr);
567 }
568}
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:1431
A view on the document.
Definition document.h:1350
QString toString() const
Returns the viewport as xml description.
@ NoPrintError
Printing succeeded.
Definition document.h:755
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:611
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:747
Page * page() const
Returns a pointer to the page where the pixmap shall be generated for.
Q_SCRIPTABLE Q_NOREPLY void start()
QStringView level(QStringView ifopt)
const QList< QKeySequence > & end()
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 Tue Mar 26 2024 11:17:35 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.