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 
109  textPage->append(text, new 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 
120 void 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 
134 void 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 
150 void 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 
160 void TextDocumentGeneratorPrivate::addMetaData(DocumentInfo::Key key, const QString &value)
161 {
162  mDocumentInfo.set(key, value);
163 }
164 
165 QList<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 
189 QList<TextDocumentGeneratorPrivate::AnnotationInfo> TextDocumentGeneratorPrivate::generateAnnotationInfos() const
190 {
191  QList<AnnotationInfo> result;
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 
209 void 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 
245 void 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 
272 TextDocumentGenerator::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 
281 TextDocumentGenerator::~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 : qAsConst(d->mLinkPositions)) {
296  delete linkPos.link;
297  }
298  d->mLinkPositions.clear();
299  for (const TextDocumentGeneratorPrivate::AnnotationPosition &annPos : qAsConst(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 
369 {
371 }
372 
374 {
375  Generator::generatePixmap(request);
376 }
377 
378 QImage 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 
440  return Document::NoPrintError;
441 }
442 
444 {
445  Q_D(const TextDocumentGenerator);
446  return d->mDocumentInfo;
447 }
448 
450 {
452  if (!d->mDocumentSynopsis.hasChildNodes()) {
453  return nullptr;
454  } else {
455  return &d->mDocumentSynopsis;
456  }
457 }
458 
459 QVariant 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 
469 {
470  static Okular::ExportFormat::List formats;
471  if (formats.isEmpty()) {
474  if (QTextDocumentWriter::supportedDocumentFormats().contains("ODF")) {
476  }
477  if (QTextDocumentWriter::supportedDocumentFormats().contains("HTML")) {
479  }
480  }
481 
482  return formats;
483 }
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 
552 TextDocumentConverter *TextDocumentGenerator::converter()
553 {
555 
556  return d->mConverter;
557 }
558 
559 void TextDocumentGenerator::setTextDocument(QTextDocument *textDocument)
560 {
562 
563  d->mDocument = textDocument;
564 
565  for (Page *p : qAsConst(d->m_document->m_pagesVector)) {
566  p->setTextPage(nullptr);
567  }
568 }
void append(const T &value)
QString toString() const
Returns the viewport as xml description.
Definition: document.cpp:5587
qreal left() const const
Collector for all the data belonging to a page.
Definition: page.h:47
const Okular::DocumentSynopsis * generateDocumentSynopsis() override
Returns the 'table of content' object of the document or 0 if no table of content is available.
bool isEmpty() const const
void warning(const QString &message, int duration)
This signal should be emitted whenever the user should be warned.
QTextDocument-based Generator.
void setObjectRects(const QList< ObjectRect * > &rects)
Sets the list of object rects of the page.
Definition: page.cpp:599
QList< QByteArray > supportedDocumentFormats()
@ Threaded
Whether the Generator supports asynchronous generation of pictures or text pages.
Definition: generator.h:202
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...
void error(const QString &message, int duration)
This signal should be emitted whenever an error occurred in the generator.
The documentation to the global Okular namespace.
Definition: action.h:16
virtual bool open(QIODevice::OpenMode mode) override
Annotation struct holds properties shared by all annotations.
Definition: annotations.h:95
Encapsulates data that describes an action.
Definition: action.h:40
int width() const
Returns the page width of the requested pixmap.
Definition: generator.cpp:598
void generatePixmap(Okular::PixmapRequest *request) override
This method can be called to trigger the generation of a new pixmap as described by request.
qreal x() const const
qreal y() const const
void setClipRect(const QRectF &rectangle, Qt::ClipOperation operation)
void append(const T &value)
TextDocumentGenerator(TextDocumentConverter *converter, const QString &configName, QObject *parent, const QVariantList &args)
Creates a new generator that uses the specified converter.
Q_SCRIPTABLE Q_NOREPLY void start()
A DOM tree that describes the Table of Contents.
Definition: document.h:1432
@ Title
The title of the document.
Definition: document.h:83
void notice(const QString &message, int duration)
This signal should be emitted whenever the user should be noticed.
QStringView level(QStringView ifopt)
void addPages(KConfigDialog *dlg) override
Does nothing by default. You need to reimplement it in your generator.
static ExportFormat standardFormat(StandardExportFormat type)
Builds a standard format for the specified type .
Definition: generator.cpp:756
int width() const const
Key
The list of predefined keys.
Definition: document.h:82
bool doCloseDocument() override
This method is called when the document is closed and not used any longer.
virtual bool canGeneratePixmap() const
This method returns whether the generator is ready to handle a new pixmap request.
Definition: generator.cpp:245
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Defines an entry for the export menu.
Definition: generator.h:78
@ PrintToFile
Whether the Generator supports export to PDF & PS through the Print Dialog.
Definition: generator.h:209
Page * page() const
Returns a pointer to the page where the pixmap shall be generated for.
Definition: generator.cpp:547
void setAttribute(const QString &name, const QString &value)
bool begin(QPaintDevice *device)
int flags() const
Returns the flags of the annotation.
Represents the textual information of a Page.
Definition: textpage.h:105
@ PDF
PDF, aka Portable Document Format.
Definition: generator.h:150
bool end()
T & top()
@ PlainText
Plain text.
Definition: generator.h:149
void addAnnotation(Annotation *annotation)
Adds a new annotation to the page.
Definition: page.cpp:681
int height() const const
void scale(qreal sx, qreal sy)
Okular::ExportFormat::List exportFormats() const override
Returns the list of additional supported export formats.
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...
@ Action
An action.
Definition: area.h:464
const T & at(int i) const const
Document::PrintError print(QPrinter &printer) override
This method is called to print the document to the given printer.
Describes a pixmap type request.
Definition: generator.h:610
int length() 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:266
bool write(const QTextDocument *document)
OpenResult
Describes the result of an open document operation.
Definition: document.h:209
qreal bottom() const const
@ Rotation0
Not rotated.
Definition: global.h:46
[Abstract Class] The information generator.
Definition: generator.h:187
bool isEmpty() const const
@ TextExtraction
Whether the Generator can extract text from the document in the form of TextPage's.
Definition: generator.h:203
int pageNumber() const
Returns the page number of the request.
Definition: generator.cpp:593
void resize(int size)
A NormalizedRect is a rectangle which can be defined by two NormalizedPoints.
Definition: area.h:188
void append(const QString &text, NormalizedRect *area)
Appends the given text with the given area as new TextEntity to the page.
Definition: textpage.cpp:246
Okular::DocumentInfo generateDocumentInfo(const QSet< DocumentInfo::Key > &keys) const override
Returns the general information object of the document.
Describes a text request.
Definition: generator.h:758
This class is an object rect that doesn't own the given pointer, i.e.
Definition: area.h:631
A view on the document.
Definition: document.h:1351
T convert(const QVariant &value)
qreal top() const const
qreal right() const const
QMimeType mimeType() const
Returns the mime type of the format.
Definition: generator.cpp:741
void setOutputFileName(const QString &fileName)
void push(const T &t)
QDomNode appendChild(const QDomNode &newChild)
@ NoPrintError
Printing succeeded.
Definition: document.h:757
TextDocumentSettings * generalSettings()
Config skeleton for TextDocumentSettingsWidget.
qreal width() const const
void setOutputFormat(QPrinter::OutputFormat format)
int number() const
Returns the number of the page in the document.
Definition: page.cpp:160
@ OpenDocumentText
OpenDocument Text format.
Definition: generator.h:151
void translate(const QPointF &offset)
void setFlags(int flags)
Sets the flags of the annotation.
int count(const T &value) const const
bool canGeneratePixmap() const override
This method returns whether the generator is ready to handle a new pixmap request.
@ External
Is stored external.
Definition: annotations.h:138
bool reparseConfig() override
By default checks if the default font has changed or not.
Okular::TextPage * textPage(Okular::TextRequest *request) override
Returns the text page for the given request.
An area with normalized coordinates that contains a reference to an object.
Definition: area.h:457
int height() const
Returns the page height of the requested pixmap.
Definition: generator.cpp:603
The DocumentInfo structure can be filled in by generators to display metadata about the currently ope...
Definition: document.h:74
@ HTML
OpenDocument Text format.
Definition: generator.h:152
const QList< QKeySequence > & end()
Q_D(Todo)
@ PrintNative
Whether the Generator supports native cross-platform printing (QPainter-based).
Definition: generator.h:207
qreal height() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Mar 23 2023 04:04:24 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.