Okular

textdocumentgenerator.cpp
1 /*
2  SPDX-FileCopyrightText: 2007 Tobias Koenig <[email protected]>
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 
29 using namespace Okular;
30 
31 /**
32  * Generic Converter Implementation
33  */
34 TextDocumentConverter::TextDocumentConverter()
35  : QObject(nullptr)
36  , d_ptr(new TextDocumentConverterPrivate)
37 {
38 }
39 
40 TextDocumentConverter::~TextDocumentConverter()
41 {
42  delete d_ptr;
43 }
44 
45 QTextDocument *TextDocumentConverter::convert(const QString &)
46 {
47  return nullptr;
48 }
49 
50 Document::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 
57 QTextDocument *TextDocumentConverter::document()
58 {
59  return d_ptr->mDocument;
60 }
61 
62 void TextDocumentConverter::setDocument(QTextDocument *document)
63 {
64  d_ptr->mDocument = document;
65 }
66 
67 DocumentViewport TextDocumentConverter::calculateViewport(QTextDocument *document, const QTextBlock &block)
68 {
69  return TextDocumentUtils::calculateViewport(document, block);
70 }
71 
72 TextDocumentGenerator *TextDocumentConverter::generator() const
73 {
74  return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr;
75 }
76 
77 /**
78  * Generic Generator Implementation
79  */
80 Okular::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  textPage->append(text, new Okular::NormalizedRect(rect.left(), rect.top(), rect.right(), rect.bottom()));
109  }
110  }
111  }
112 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
113  q->userMutex()->unlock();
114 #endif
115 
116  return textPage;
117 }
118 
119 void TextDocumentGeneratorPrivate::addAction(Action *action, int cursorBegin, int cursorEnd)
120 {
121  if (!action)
122  return;
123 
124  LinkPosition position;
125  position.link = action;
126  position.startPosition = cursorBegin;
127  position.endPosition = cursorEnd;
128 
129  mLinkPositions.append(position);
130 }
131 
132 void TextDocumentGeneratorPrivate::addAnnotation(Annotation *annotation, int cursorBegin, int cursorEnd)
133 {
134  if (!annotation)
135  return;
136 
137  annotation->setFlags(annotation->flags() | Okular::Annotation::External);
138 
139  AnnotationPosition position;
140  position.annotation = annotation;
141  position.startPosition = cursorBegin;
142  position.endPosition = cursorEnd;
143 
144  mAnnotationPositions.append(position);
145 }
146 
147 void TextDocumentGeneratorPrivate::addTitle(int level, const QString &title, const QTextBlock &block)
148 {
149  TitlePosition position;
150  position.level = level;
151  position.title = title;
152  position.block = block;
153 
154  mTitlePositions.append(position);
155 }
156 
157 void TextDocumentGeneratorPrivate::addMetaData(const QString &key, const QString &value, const QString &title)
158 {
159  mDocumentInfo.set(key, value, title);
160 }
161 
162 void TextDocumentGeneratorPrivate::addMetaData(DocumentInfo::Key key, const QString &value)
163 {
164  mDocumentInfo.set(key, value);
165 }
166 
167 QList<TextDocumentGeneratorPrivate::LinkInfo> TextDocumentGeneratorPrivate::generateLinkInfos() const
168 {
169  QList<LinkInfo> result;
170 
171  for (int i = 0; i < mLinkPositions.count(); ++i) {
172  const LinkPosition &linkPosition = mLinkPositions[i];
173 
174  const QVector<QRectF> rects = TextDocumentUtils::calculateBoundingRects(mDocument, linkPosition.startPosition, linkPosition.endPosition);
175 
176  for (int i = 0; i < rects.count(); ++i) {
177  const QRectF &rect = rects[i];
178 
179  LinkInfo info;
180  info.link = linkPosition.link;
181  info.ownsLink = i == 0;
182  info.page = std::floor(rect.y());
183  info.boundingRect = QRectF(rect.x(), rect.y() - info.page, rect.width(), rect.height());
184  result.append(info);
185  }
186  }
187 
188  return result;
189 }
190 
191 QList<TextDocumentGeneratorPrivate::AnnotationInfo> TextDocumentGeneratorPrivate::generateAnnotationInfos() const
192 {
193  QList<AnnotationInfo> result;
194 
195  for (int i = 0; i < mAnnotationPositions.count(); ++i) {
196  const AnnotationPosition &annotationPosition = mAnnotationPositions[i];
197 
198  AnnotationInfo info;
199  info.annotation = annotationPosition.annotation;
200 
201  TextDocumentUtils::calculateBoundingRect(mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page);
202 
203  if (info.page >= 0)
204  result.append(info);
205  }
206 
207  return result;
208 }
209 
210 void TextDocumentGeneratorPrivate::generateTitleInfos()
211 {
212  QStack<QPair<int, QDomNode>> parentNodeStack;
213 
214  QDomNode parentNode = mDocumentSynopsis;
215 
216  parentNodeStack.push(qMakePair(0, parentNode));
217 
218  for (int i = 0; i < mTitlePositions.count(); ++i) {
219  const TitlePosition &position = mTitlePositions[i];
220 
221  Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport(mDocument, position.block);
222 
223  QDomElement item = mDocumentSynopsis.createElement(position.title);
224  item.setAttribute(QStringLiteral("Viewport"), viewport.toString());
225 
226  int headingLevel = position.level;
227 
228  // we need a parent, which has to be at a higher heading level than this heading level
229  // so we just work through the stack
230  while (!parentNodeStack.isEmpty()) {
231  int parentLevel = parentNodeStack.top().first;
232  if (parentLevel < headingLevel) {
233  // this is OK as a parent
234  parentNode = parentNodeStack.top().second;
235  break;
236  } else {
237  // we'll need to be further into the stack
238  parentNodeStack.pop();
239  }
240  }
241  parentNode.appendChild(item);
242  parentNodeStack.push(qMakePair(headingLevel, QDomNode(item)));
243  }
244 }
245 
246 void TextDocumentGeneratorPrivate::initializeGenerator()
247 {
249 
250  mConverter->d_ptr->mParent = q->d_func();
251 
252  if (mGeneralSettings) {
253  mFont = mGeneralSettings->font();
254  }
255 
256  q->setFeature(Generator::TextExtraction);
257  q->setFeature(Generator::PrintNative);
258  q->setFeature(Generator::PrintToFile);
259 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
260  q->setFeature(Generator::Threaded);
261 #endif
262 
263  QObject::connect(mConverter, &TextDocumentConverter::addAction, q, [this](Action *a, int cb, int ce) { addAction(a, cb, ce); });
264  QObject::connect(mConverter, &TextDocumentConverter::addAnnotation, q, [this](Annotation *a, int cb, int ce) { addAnnotation(a, cb, ce); });
265  QObject::connect(mConverter, &TextDocumentConverter::addTitle, q, [this](int l, const QString &t, const QTextBlock &b) { addTitle(l, t, b); });
266  QObject::connect(mConverter, QOverload<const QString &, const QString &, const QString &>::of(&TextDocumentConverter::addMetaData), q, [this](const QString &k, const QString &v, const QString &t) { addMetaData(k, v, t); });
267  QObject::connect(mConverter, QOverload<DocumentInfo::Key, const QString &>::of(&TextDocumentConverter::addMetaData), q, [this](DocumentInfo::Key k, const QString &v) { addMetaData(k, v); });
268 
269  QObject::connect(mConverter, &TextDocumentConverter::error, q, &Generator::error);
270  QObject::connect(mConverter, &TextDocumentConverter::warning, q, &Generator::warning);
271  QObject::connect(mConverter, &TextDocumentConverter::notice, q, &Generator::notice);
272 }
273 
274 TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args)
275  : Okular::Generator(*new TextDocumentGeneratorPrivate(converter), parent, args)
276 {
278  d->mGeneralSettings = new TextDocumentSettings(configName, this);
279 
280  d->initializeGenerator();
281 }
282 
283 TextDocumentGenerator::~TextDocumentGenerator()
284 {
285 }
286 
288 {
290  const Document::OpenResult openResult = d->mConverter->convertWithPassword(fileName, password);
291 
292  if (openResult != Document::OpenSuccess) {
293  d->mDocument = nullptr;
294 
295  // loading failed, cleanup all the stuff eventually gathered from the converter
296  d->mTitlePositions.clear();
297  for (const TextDocumentGeneratorPrivate::LinkPosition &linkPos : qAsConst(d->mLinkPositions)) {
298  delete linkPos.link;
299  }
300  d->mLinkPositions.clear();
301  for (const TextDocumentGeneratorPrivate::AnnotationPosition &annPos : qAsConst(d->mAnnotationPositions)) {
302  delete annPos.annotation;
303  }
304  d->mAnnotationPositions.clear();
305 
306  return openResult;
307  }
308  d->mDocument = d->mConverter->document();
309 
310  d->generateTitleInfos();
311  const QList<TextDocumentGeneratorPrivate::LinkInfo> linkInfos = d->generateLinkInfos();
312  const QList<TextDocumentGeneratorPrivate::AnnotationInfo> annotationInfos = d->generateAnnotationInfos();
313 
314  pagesVector.resize(d->mDocument->pageCount());
315 
316  const QSize size = d->mDocument->pageSize().toSize();
317 
318  QVector<QLinkedList<Okular::ObjectRect *>> objects(d->mDocument->pageCount());
319  for (const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos) {
320  // in case that the converter report bogus link info data, do not assert here
321  if (info.page < 0 || info.page >= objects.count())
322  continue;
323 
324  const QRectF rect = info.boundingRect;
325  if (info.ownsLink) {
326  objects[info.page].append(new Okular::ObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link));
327  } else {
328  objects[info.page].append(new Okular::NonOwningObjectRect(rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link));
329  }
330  }
331 
332  QVector<QLinkedList<Okular::Annotation *>> annots(d->mDocument->pageCount());
333  for (const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos) {
334  annots[info.page].append(info.annotation);
335  }
336 
337  for (int i = 0; i < d->mDocument->pageCount(); ++i) {
338  Okular::Page *page = new Okular::Page(i, size.width(), size.height(), Okular::Rotation0);
339  pagesVector[i] = page;
340 
341  if (!objects.at(i).isEmpty()) {
342  page->setObjectRects(objects.at(i));
343  }
344  QLinkedList<Okular::Annotation *>::ConstIterator annIt = annots.at(i).begin(), annEnd = annots.at(i).end();
345  for (; annIt != annEnd; ++annIt) {
346  page->addAnnotation(*annIt);
347  }
348  }
349 
350  return openResult;
351 }
352 
354 {
356  delete d->mDocument;
357  d->mDocument = nullptr;
358 
359  d->mTitlePositions.clear();
360  d->mLinkPositions.clear();
361  d->mAnnotationPositions.clear();
362  // do not use clear() for the following two, otherwise they change type
363  d->mDocumentInfo = Okular::DocumentInfo();
364  d->mDocumentSynopsis = Okular::DocumentSynopsis();
365 
366  return true;
367 }
368 
370 {
372 }
373 
375 {
376  Generator::generatePixmap(request);
377 }
378 
379 QImage TextDocumentGeneratorPrivate::image(PixmapRequest *request)
380 {
381  if (!mDocument)
382  return QImage();
383 
384 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
386 #endif
387 
388  QImage image(request->width(), request->height(), QImage::Format_ARGB32);
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 false;
436 
437  d->mDocument->print(&printer);
438 
439  return true;
440 }
441 
443 {
444  Q_D(const TextDocumentGenerator);
445  return d->mDocumentInfo;
446 }
447 
449 {
451  if (!d->mDocumentSynopsis.hasChildNodes())
452  return nullptr;
453  else
454  return &d->mDocumentSynopsis;
455 }
456 
457 QVariant 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 
467 {
468  static Okular::ExportFormat::List formats;
469  if (formats.isEmpty()) {
472  if (QTextDocumentWriter::supportedDocumentFormats().contains("ODF")) {
474  }
475  if (QTextDocumentWriter::supportedDocumentFormats().contains("HTML")) {
477  }
478  }
479 
480  return formats;
481 }
482 
484 {
486  if (!d->mDocument)
487  return false;
488 
489  if (format.mimeType().name() == QLatin1String("application/pdf")) {
490  QFile file(fileName);
491  if (!file.open(QIODevice::WriteOnly))
492  return false;
493 
496  printer.setOutputFileName(fileName);
497  d->mDocument->print(&printer);
498 
499  return true;
500  } else if (format.mimeType().name() == QLatin1String("text/plain")) {
501  QFile file(fileName);
502  if (!file.open(QIODevice::WriteOnly))
503  return false;
504 
505  QTextStream out(&file);
506  out << d->mDocument->toPlainText();
507 
508  return true;
509  } else if (format.mimeType().name() == QLatin1String("application/vnd.oasis.opendocument.text")) {
510  QTextDocumentWriter odfWriter(fileName, "odf");
511 
512  return odfWriter.write(d->mDocument);
513  } else if (format.mimeType().name() == QLatin1String("text/html")) {
514  QTextDocumentWriter odfWriter(fileName, "html");
515 
516  return odfWriter.write(d->mDocument);
517  }
518  return false;
519 }
520 
522 {
524  const QFont newFont = d->mGeneralSettings->font();
525 
526  if (newFont != d->mFont) {
527  d->mFont = newFont;
528  return true;
529  }
530 
531  return false;
532 }
533 
535 {
536  qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator";
537  return;
538 }
539 
541 {
543 
544  return d->mGeneralSettings;
545 }
546 
547 TextDocumentConverter *TextDocumentGenerator::converter()
548 {
550 
551  return d->mConverter;
552 }
553 
554 void TextDocumentGenerator::setTextDocument(QTextDocument *textDocument)
555 {
557 
558  d->mDocument = textDocument;
559 
560  for (Page *p : qAsConst(d->m_document->m_pagesVector)) {
561  p->setTextPage(nullptr);
562  }
563 }
Whether the Generator supports export to PDF & PS through the Print Dialog.
Definition: generator.h:209
T convert(const QVariant &value)
int pageNumber() const
Returns the page number of the request.
Definition: generator.cpp:600
bool canGeneratePixmap() const override
This method returns whether the generator is ready to handle a new pixmap request.
int width() const const
bool end()
Represents the textual information of a Page.
Definition: textpage.h:105
QDomNode appendChild(const QDomNode &newChild)
void append(const T &value)
void notice(const QString &message, int duration)
This signal should be emitted whenever the user should be noticed.
qreal x() const const
qreal y() const const
void setOutputFileName(const QString &fileName)
QLinkedList::iterator begin()
virtual bool canGeneratePixmap() const
This method returns whether the generator is ready to handle a new pixmap request.
Definition: generator.cpp:240
void push(const T &t)
void scale(qreal sx, qreal sy)
int number() const
Returns the number of the page in the document.
Definition: page.cpp:156
A NormalizedRect is a rectangle which can be defined by two NormalizedPoints.
Definition: area.h:189
qreal top() const const
Describes a text request.
Definition: generator.h:782
TextDocumentSettings * generalSettings()
Config skeleton for TextDocumentSettingsWidget.
global.h
Definition: action.h:16
Not rotated.
Definition: global.h:46
qreal left() const const
void setFlags(int flags)
Sets the flags of the annotation.
bool doCloseDocument() override
This method is called when the document is closed and not used any longer.
Key
The list of predefined keys.
Definition: document.h:82
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...
int height() const
Returns the page height of the requested pixmap.
Definition: generator.cpp:610
OpenResult
Describes the result of an open document operation.
Definition: document.h:209
The title of the document.
Definition: document.h:83
bool print(QPrinter &printer) override
This method is called to print the document to the given printer.
Whether the Generator supports asynchronous generation of pictures or text pages. ...
Definition: generator.h:202
qreal bottom() const const
static ExportFormat standardFormat(StandardExportFormat type)
Builds a standard format for the specified type .
Definition: generator.cpp:761
void append(const T &value)
void warning(const QString &message, int duration)
This signal should be emitted whenever the user should be warned.
void resize(int size)
Okular::DocumentInfo generateDocumentInfo(const QSet< DocumentInfo::Key > &keys) const override
Returns the general information object of the document.
void setObjectRects(const QLinkedList< ObjectRect * > &rects)
Sets the list of object rects of the page.
Definition: page.cpp:567
void fill(uint pixelValue)
An area with normalized coordinates that contains a reference to an object.
Definition: area.h:452
int width() const
Returns the page width of the requested pixmap.
Definition: generator.cpp:605
void setAttribute(const QString &name, const QString &value)
bool isEmpty() const const
Is stored external.
Definition: annotations.h:139
QString fileName() const
Returns the filename of the source.
int flags() const
Returns the flags of the annotation.
OpenDocument Text format.
Definition: generator.h:151
Page * page() const
Returns a pointer to the page where the pixmap shall be generated for.
Definition: generator.cpp:554
QMimeType mimeType() const
Returns the mime type of the format.
Definition: generator.cpp:746
This class is an object rect that doesn&#39;t own the given pointer, i.e.
Definition: area.h:626
Collector for all the data belonging to a page.
Definition: page.h:49
virtual QImage image(PixmapRequest *request)
Returns the image of the page as specified in the passed pixmap request.
Definition: generator.cpp:324
void error(const QString &message, int duration)
This signal should be emitted whenever an error occurred in the generator.
virtual bool open(QIODevice::OpenMode mode) override
bool reparseConfig() override
By default checks if the default font has changed or not.
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...
Okular::TextPage * textPage(Okular::TextRequest *request) override
Returns the text page for the given request.
qreal right() const const
void generatePixmap(Okular::PixmapRequest *request) override
This method can be called to trigger the generation of a new pixmap as described by request...
Whether the Generator supports native cross-platform printing (QPainter-based).
Definition: generator.h:207
Encapsulates data that describes an action.
Definition: action.h:40
The DocumentInfo structure can be filled in by generators to display metadata about the currently ope...
Definition: document.h:74
QTextDocument-based Generator.
PDF, aka Portable Document Format.
Definition: generator.h:150
TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args)
Creates a new generator that uses the specified converter.
const QList< QKeySequence > & end()
qreal width() const const
void setClipRect(const QRectF &rectangle, Qt::ClipOperation operation)
Okular::ExportFormat::List exportFormats() const override
Returns the list of additional supported export formats.
Defines an entry for the export menu.
Definition: generator.h:78
bool isEmpty() const const
Annotation struct holds properties shared by all annotations.
Definition: annotations.h:96
bool write(const QTextDocument *document)
OpenDocument Text format.
Definition: generator.h:152
int height() const const
int count(const T &value) const const
virtual void generatePixmap(PixmapRequest *request)
This method can be called to trigger the generation of a new pixmap as described by request...
Definition: generator.cpp:261
int length() const const
void translate(const QPointF &offset)
qreal height() const const
A view on the document.
Definition: document.h:1301
Whether the Generator can extract text from the document in the form of TextPage&#39;s.
Definition: generator.h:203
void addAnnotation(Annotation *annotation)
Adds a new annotation to the page.
Definition: page.cpp:644
QString toString() const
Returns the viewport as xml description.
Definition: document.cpp:5198
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setOutputFormat(QPrinter::OutputFormat format)
bool begin(QPaintDevice *device)
A DOM tree that describes the Table of Contents.
Definition: document.h:1382
void append(const QString &text, NormalizedRect *area)
Appends the given text with the given area as new TextEntity to the page.
Definition: textpage.cpp:243
void addPages(KConfigDialog *dlg) override
Does nothing by default. You need to reimplement it in your generator.
Describes a pixmap type request.
Definition: generator.h:634
QList< QByteArray > supportedDocumentFormats()
[Abstract Class] The information generator.
Definition: generator.h:187
const Okular::DocumentSynopsis * generateDocumentSynopsis() override
Returns the &#39;table of content&#39; object of the document or 0 if no table of content is available...
T & top()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Dec 5 2021 22:32:15 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.