1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qabstracttextdocumentlayout.h>
5#include <qtextformat.h>
6#include "qtextdocument_p.h"
7#include "qtextengine_p.h"
8#include "qtextlist.h"
9
10#include "qabstracttextdocumentlayout_p.h"
11
12QT_BEGIN_NAMESPACE
13
14QAbstractTextDocumentLayoutPrivate::~QAbstractTextDocumentLayoutPrivate()
15{
16}
17
18QTextObjectInterface::~QTextObjectInterface()
19{
20}
21
22/*!
23 \class QAbstractTextDocumentLayout
24 \reentrant
25
26 \brief The QAbstractTextDocumentLayout class is an abstract base
27 class used to implement custom layouts for QTextDocuments.
28 \inmodule QtGui
29
30 \ingroup richtext-processing
31
32 The standard layout provided by Qt can handle simple word processing
33 including inline images, lists and tables.
34
35 Some applications, e.g., a word processor or a DTP application might need
36 more features than the ones provided by Qt's layout engine, in which case
37 you can subclass QAbstractTextDocumentLayout to provide custom layout
38 behavior for your text documents.
39
40 An instance of the QAbstractTextDocumentLayout subclass can be installed
41 on a QTextDocument object with the
42 \l{QTextDocument::}{setDocumentLayout()} function.
43
44 You can insert custom objects into a QTextDocument; see the
45 QTextObjectInterface class description for details.
46
47 \sa QTextObjectInterface
48*/
49
50/*!
51 \class QTextObjectInterface
52 \brief The QTextObjectInterface class allows drawing of
53 custom text objects in \l{QTextDocument}s.
54 \since 4.5
55 \inmodule QtGui
56
57 A text object describes the structure of one or more elements in a
58 text document; for instance, images imported from HTML are
59 implemented using text objects. A text object knows how to lay out
60 and draw its elements when a document is being rendered.
61
62 Qt allows custom text objects to be inserted into a document by
63 registering a custom \l{QTextCharFormat::objectType()}{object
64 type} with QTextCharFormat. A QTextObjectInterface must also be
65 implemented for this type and be
66 \l{QAbstractTextDocumentLayout::registerHandler()}{registered}
67 with the QAbstractTextDocumentLayout of the document. When the
68 object type is encountered while rendering a QTextDocument, the
69 intrinsicSize() and drawObject() functions of the interface are
70 called.
71
72 The following list explains the required steps of inserting a
73 custom text object into a document:
74
75 \list
76 \li Choose an \a objectType. The \a objectType is an integer with a
77 value greater or equal to QTextFormat::UserObject.
78 \li Create a QTextCharFormat object and set the object type to the
79 chosen type using the setObjectType() function.
80 \li Implement the QTextObjectInterface class.
81 \li Call QAbstractTextDocumentLayout::registerHandler() with an instance of your
82 QTextObjectInterface subclass to register your object type.
83 \li Insert QChar::ObjectReplacementCharacter with the aforementioned
84 QTextCharFormat of the chosen object type into the document.
85 As mentioned, the functions of QTextObjectInterface
86 \l{QTextObjectInterface::}{intrinsicSize()} and
87 \l{QTextObjectInterface::}{drawObject()} will then be called with the
88 QTextFormat as parameter whenever the replacement character is
89 encountered.
90 \endlist
91
92 A class implementing a text object needs to inherit both QObject
93 and QTextObjectInterface. QObject must be the first class
94 inherited. For instance:
95
96 \snippet qtextobject/textobjectinterface.h 0
97
98 The data of a text object is usually stored in the QTextCharFormat
99 using QTextCharFormat::setProperty(), and then retrieved with
100 QTextCharFormat::property().
101
102 \warning Copy and Paste operations ignore custom text objects.
103
104 \sa {Text Object Example}, QTextCharFormat, QTextLayout
105*/
106
107/*!
108 \fn QTextObjectInterface::~QTextObjectInterface()
109
110 Destroys this QTextObjectInterface.
111*/
112
113/*!
114 \fn virtual QSizeF QTextObjectInterface::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0
115
116 The intrinsicSize() function returns the size of the text object
117 represented by \a format in the given document (\a doc) at the
118 given position (\a posInDocument).
119
120 The size calculated will be used for subsequent calls to
121 drawObject() for this \a format.
122
123 \sa drawObject()
124*/
125
126/*!
127 \fn virtual void QTextObjectInterface::drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0
128
129 Draws this text object using the specified \a painter.
130
131 The size of the rectangle, \a rect, to draw in is the size
132 previously calculated by intrinsicSize(). The rectangles position
133 is relative to the \a painter.
134
135 You also get the document (\a doc) and the position (\a
136 posInDocument) of the \a format in that document.
137
138 \sa intrinsicSize()
139*/
140
141/*!
142 \fn void QAbstractTextDocumentLayout::update(const QRectF &rect)
143
144 This signal is emitted when the rectangle \a rect has been updated.
145
146 Subclasses of QAbstractTextDocumentLayout should emit this signal when
147 the layout of the contents change in order to repaint.
148*/
149
150/*!
151 \fn void QAbstractTextDocumentLayout::updateBlock(const QTextBlock &block)
152 \since 4.4
153
154 This signal is emitted when the specified \a block has been updated.
155
156 Subclasses of QAbstractTextDocumentLayout should emit this signal when
157 the layout of \a block has changed in order to repaint.
158*/
159
160/*!
161 \fn void QAbstractTextDocumentLayout::documentSizeChanged(const QSizeF &newSize)
162
163 This signal is emitted when the size of the document layout changes to
164 \a newSize.
165
166 Subclasses of QAbstractTextDocumentLayout should emit this signal when the
167 document's entire layout size changes. This signal is useful for widgets
168 that display text documents since it enables them to update their scroll
169 bars correctly.
170
171 \sa documentSize()
172*/
173
174/*!
175 \fn void QAbstractTextDocumentLayout::pageCountChanged(int newPages)
176
177 This signal is emitted when the number of pages in the layout changes;
178 \a newPages is the updated page count.
179
180 Subclasses of QAbstractTextDocumentLayout should emit this signal when
181 the number of pages in the layout has changed. Changes to the page count
182 are caused by changes to the layout or the document content itself.
183
184 \sa pageCount()
185*/
186
187/*!
188 \fn int QAbstractTextDocumentLayout::pageCount() const
189
190 Returns the number of pages contained in the layout.
191
192 \sa pageCountChanged()
193*/
194
195/*!
196 \fn QSizeF QAbstractTextDocumentLayout::documentSize() const
197
198 Returns the total size of the document's layout.
199
200 This information can be used by display widgets to update their scroll bars
201 correctly.
202
203 \sa documentSizeChanged(), QTextDocument::pageSize
204*/
205
206/*!
207 \fn void QAbstractTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
208
209 Draws the layout with the given \a painter using the given \a context.
210*/
211
212/*!
213 \fn int QAbstractTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
214
215 Returns the cursor position for the given \a point with the specified
216 \a accuracy. Returns -1 if no valid cursor position was found.
217*/
218
219/*!
220 \fn void QAbstractTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded)
221
222 This function is called whenever the contents of the document change. A
223 change occurs when text is inserted, removed, or a combination of these
224 two. The change is specified by \a position, \a charsRemoved, and
225 \a charsAdded corresponding to the starting character position of the
226 change, the number of characters removed from the document, and the
227 number of characters added.
228
229 For example, when inserting the text "Hello" into an empty document,
230 \a charsRemoved would be 0 and \a charsAdded would be 5 (the length of
231 the string).
232
233 Replacing text is a combination of removing and inserting. For example, if
234 the text "Hello" gets replaced by "Hi", \a charsRemoved would be 5 and
235 \a charsAdded would be 2.
236
237 For subclasses of QAbstractTextDocumentLayout, this is the central function
238 where a large portion of the work to lay out and position document contents
239 is done.
240
241 For example, in a subclass that only arranges blocks of text, an
242 implementation of this function would have to do the following:
243
244 \list
245 \li Determine the list of changed \l{QTextBlock}(s) using the parameters
246 provided.
247 \li Each QTextBlock object's corresponding QTextLayout object needs to
248 be processed. You can access the \l{QTextBlock}'s layout using the
249 QTextBlock::layout() function. This processing should take the
250 document's page size into consideration.
251 \li If the total number of pages changed, the pageCountChanged() signal
252 should be emitted.
253 \li If the total size changed, the documentSizeChanged() signal should
254 be emitted.
255 \li The update() signal should be emitted to schedule a repaint of areas
256 in the layout that require repainting.
257 \endlist
258
259 \sa QTextLayout
260*/
261
262/*!
263 \class QAbstractTextDocumentLayout::PaintContext
264 \reentrant
265 \inmodule QtGui
266
267 \brief The QAbstractTextDocumentLayout::PaintContext class is a convenience
268 class defining the parameters used when painting a document's layout.
269
270 A paint context is used when rendering custom layouts for QTextDocuments
271 with the QAbstractTextDocumentLayout::draw() function. It is specified by
272 a \l {cursorPosition}{cursor position}, \l {palette}{default text color},
273 \l clip rectangle and a collection of \l selections.
274
275 \sa QAbstractTextDocumentLayout
276*/
277
278/*!
279 \fn QAbstractTextDocumentLayout::PaintContext::PaintContext()
280 \internal
281*/
282
283/*!
284 \variable QAbstractTextDocumentLayout::PaintContext::cursorPosition
285
286 \brief the position within the document, where the cursor line should be
287 drawn.
288
289 The default value is -1.
290*/
291
292/*!
293 \variable QAbstractTextDocumentLayout::PaintContext::palette
294
295 \brief the default color that is used for the text, when no color is
296 specified.
297
298 The default value is the application's default palette.
299*/
300
301/*!
302 \variable QAbstractTextDocumentLayout::PaintContext::clip
303
304 \brief a hint to the layout specifying the area around paragraphs, frames
305 or text require painting.
306
307 Everything outside of this rectangle does not need to be painted.
308
309 Specifying a clip rectangle can speed up drawing of large documents
310 significantly. Note that the clip rectangle is in document coordinates (not
311 in viewport coordinates). It is not a substitute for a clip region set on
312 the painter but merely a hint.
313
314 The default value is a null rectangle indicating everything needs to be
315 painted.
316*/
317
318/*!
319 \variable QAbstractTextDocumentLayout::PaintContext::selections
320
321 \brief the collection of selections that will be rendered when passing this
322 paint context to QAbstractTextDocumentLayout's draw() function.
323
324 The default value is an empty list indicating no selection.
325*/
326
327/*!
328 \class QAbstractTextDocumentLayout::Selection
329 \reentrant
330 \inmodule QtGui
331
332 \brief The QAbstractTextDocumentLayout::Selection class is a convenience
333 class defining the parameters of a selection.
334
335 A selection can be used to specify a part of a document that should be
336 highlighted when drawing custom layouts for QTextDocuments with the
337 QAbstractTextDocumentLayout::draw() function. It is specified using
338 \l cursor and a \l format.
339
340 \sa QAbstractTextDocumentLayout, PaintContext
341*/
342
343/*!
344 \variable QAbstractTextDocumentLayout::Selection::format
345
346 \brief the format of the selection
347
348 The default value is QTextFormat::InvalidFormat.
349*/
350
351/*!
352 \variable QAbstractTextDocumentLayout::Selection::cursor
353 \brief the selection's cursor
354
355 The default value is a null cursor.
356*/
357
358/*!
359 Creates a new text document layout for the given \a document.
360*/
361QAbstractTextDocumentLayout::QAbstractTextDocumentLayout(QTextDocument *document)
362 : QObject(*new QAbstractTextDocumentLayoutPrivate, document)
363{
364 Q_D(QAbstractTextDocumentLayout);
365 d->setDocument(document);
366}
367
368/*!
369 \internal
370*/
371QAbstractTextDocumentLayout::QAbstractTextDocumentLayout(QAbstractTextDocumentLayoutPrivate &p, QTextDocument *document)
372 :QObject(p, document)
373{
374 Q_D(QAbstractTextDocumentLayout);
375 d->setDocument(document);
376}
377
378/*!
379 \internal
380*/
381QAbstractTextDocumentLayout::~QAbstractTextDocumentLayout()
382{
383}
384
385/*!
386 Registers the given \a component as a handler for items of the given \a objectType.
387
388 \note registerHandler() has to be called once for each object type. This
389 means that there is only one handler for multiple replacement characters
390 of the same object type.
391
392 The text document layout does not take ownership of \c component.
393*/
394void QAbstractTextDocumentLayout::registerHandler(int objectType, QObject *component)
395{
396 Q_D(QAbstractTextDocumentLayout);
397
398 QTextObjectInterface *iface = qobject_cast<QTextObjectInterface *>(object: component);
399 if (!iface)
400 return; // ### print error message on terminal?
401
402 QObjectPrivate::connect(sender: component, signal: &QObject::destroyed, receiverPrivate: d,
403 slot: &QAbstractTextDocumentLayoutPrivate::_q_handlerDestroyed);
404
405 QTextObjectHandler h;
406 h.iface = iface;
407 h.component = component;
408 d->handlers.insert(key: objectType, value: h);
409}
410
411/*!
412 \since 5.2
413
414 Unregisters the given \a component as a handler for items of the given \a objectType, or
415 any handler if the \a component is not specified.
416*/
417void QAbstractTextDocumentLayout::unregisterHandler(int objectType, QObject *component)
418{
419 Q_D(QAbstractTextDocumentLayout);
420
421 const auto it = d->handlers.constFind(key: objectType);
422 if (it != d->handlers.cend() && (!component || component == it->component)) {
423 if (component)
424 QObjectPrivate::disconnect(sender: component, signal: &QObject::destroyed, receiverPrivate: d,
425 slot: &QAbstractTextDocumentLayoutPrivate::_q_handlerDestroyed);
426 d->handlers.erase(it);
427 }
428}
429
430/*!
431 Returns a handler for objects of the given \a objectType.
432*/
433QTextObjectInterface *QAbstractTextDocumentLayout::handlerForObject(int objectType) const
434{
435 Q_D(const QAbstractTextDocumentLayout);
436
437 QTextObjectHandler handler = d->handlers.value(key: objectType);
438 if (!handler.component)
439 return nullptr;
440
441 return handler.iface;
442}
443
444/*!
445 Sets the size of the inline object \a item corresponding to the text
446 \a format.
447
448 \a posInDocument specifies the position of the object within the document.
449
450 The default implementation resizes the \a item to the size returned by
451 the object handler's intrinsicSize() function. This function is called only
452 within Qt. Subclasses can reimplement this function to customize the
453 resizing of inline objects.
454*/
455void QAbstractTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
456{
457 Q_D(QAbstractTextDocumentLayout);
458
459 QTextCharFormat f = format.toCharFormat();
460 Q_ASSERT(f.isValid());
461 QTextObjectHandler handler = d->handlers.value(key: f.objectType());
462 if (!handler.component)
463 return;
464
465 QSizeF s = handler.iface->intrinsicSize(doc: document(), posInDocument, format);
466 item.setWidth(s.width());
467 item.setAscent(s.height());
468 item.setDescent(0);
469}
470
471/*!
472 Lays out the inline object \a item using the given text \a format.
473
474 \a posInDocument specifies the position of the object within the document.
475
476 The default implementation does nothing. This function is called only
477 within Qt. Subclasses can reimplement this function to customize the
478 position of inline objects.
479
480 \sa drawInlineObject()
481*/
482void QAbstractTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
483{
484 Q_UNUSED(item);
485 Q_UNUSED(posInDocument);
486 Q_UNUSED(format);
487}
488
489/*!
490 \fn void QAbstractTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int posInDocument, const QTextFormat &format)
491
492 This function is called to draw the inline object, \a object, with the
493 given \a painter within the rectangle specified by \a rect using the
494 specified text \a format.
495
496 \a posInDocument specifies the position of the object within the document.
497
498 The default implementation calls drawObject() on the object handlers. This
499 function is called only within Qt. Subclasses can reimplement this function
500 to customize the drawing of inline objects.
501
502 \sa draw()
503*/
504void QAbstractTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
505 int posInDocument, const QTextFormat &format)
506{
507 Q_UNUSED(item);
508 Q_D(QAbstractTextDocumentLayout);
509
510 QTextCharFormat f = format.toCharFormat();
511 Q_ASSERT(f.isValid());
512 QTextObjectHandler handler = d->handlers.value(key: f.objectType());
513 if (!handler.component)
514 return;
515
516 handler.iface->drawObject(painter: p, rect, doc: document(), posInDocument, format);
517}
518
519void QAbstractTextDocumentLayoutPrivate::_q_handlerDestroyed(QObject *obj)
520{
521 HandlerHash::Iterator it = handlers.begin();
522 while (it != handlers.end())
523 if ((*it).component == obj)
524 it = handlers.erase(it);
525 else
526 ++it;
527}
528
529/*!
530 \internal
531
532 Returns the index of the format at position \a pos.
533*/
534int QAbstractTextDocumentLayout::formatIndex(int pos)
535{
536 QTextDocumentPrivate *pieceTable = QTextDocumentPrivate::get(document: qobject_cast<QTextDocument *>(object: parent()));
537 return pieceTable->find(pos).value()->format;
538}
539
540/*!
541 \fn QTextCharFormat QAbstractTextDocumentLayout::format(int position)
542
543 Returns the character format that is applicable at the given \a position.
544*/
545QTextCharFormat QAbstractTextDocumentLayout::format(int pos)
546{
547 QTextDocumentPrivate *pieceTable = QTextDocumentPrivate::get(document: qobject_cast<QTextDocument *>(object: parent()));
548 int idx = pieceTable->find(pos).value()->format;
549 return pieceTable->formatCollection()->charFormat(index: idx);
550}
551
552
553
554/*!
555 Returns the text document that this layout is operating on.
556*/
557QTextDocument *QAbstractTextDocumentLayout::document() const
558{
559 Q_D(const QAbstractTextDocumentLayout);
560 return d->document;
561}
562
563/*!
564 \fn QString QAbstractTextDocumentLayout::anchorAt(const QPointF &position) const
565
566 Returns the reference of the anchor the given \a position, or an empty
567 string if no anchor exists at that point.
568*/
569QString QAbstractTextDocumentLayout::anchorAt(const QPointF& pos) const
570{
571 QTextCharFormat fmt = formatAt(pos).toCharFormat();
572 return fmt.anchorHref();
573}
574
575/*!
576 \since 5.8
577
578 Returns the source of the image at the given position \a pos, or an empty
579 string if no image exists at that point.
580*/
581QString QAbstractTextDocumentLayout::imageAt(const QPointF &pos) const
582{
583 QTextImageFormat fmt = formatAt(pos).toImageFormat();
584 return fmt.name();
585}
586
587/*!
588 \since 5.8
589
590 Returns the text format at the given position \a pos.
591*/
592QTextFormat QAbstractTextDocumentLayout::formatAt(const QPointF &pos) const
593{
594 int cursorPos = hitTest(point: pos, accuracy: Qt::ExactHit);
595 if (cursorPos == -1)
596 return QTextFormat();
597
598 // compensate for preedit in the hit text block
599 QTextBlock block = document()->firstBlock();
600 while (block.isValid()) {
601 QRectF blockBr = blockBoundingRect(block);
602 if (blockBr.contains(p: pos)) {
603 QTextLayout *layout = block.layout();
604 int relativeCursorPos = cursorPos - block.position();
605 const int preeditLength = layout ? layout->preeditAreaText().size() : 0;
606 if (preeditLength > 0 && relativeCursorPos > layout->preeditAreaPosition())
607 cursorPos -= qMin(a: cursorPos - layout->preeditAreaPosition(), b: preeditLength);
608 break;
609 }
610 block = block.next();
611 }
612
613 const QTextDocumentPrivate *pieceTable = QTextDocumentPrivate::get(document: qobject_cast<const QTextDocument *>(object: parent()));
614 QTextDocumentPrivate::FragmentIterator it = pieceTable->find(pos: cursorPos);
615 return pieceTable->formatCollection()->format(idx: it->format);
616}
617
618/*!
619 \since 5.14
620
621 Returns the block (probably a list item) whose \l{QTextBlockFormat::marker()}{marker}
622 is found at the given position \a pos.
623*/
624QTextBlock QAbstractTextDocumentLayout::blockWithMarkerAt(const QPointF &pos) const
625{
626 QTextBlock block = document()->firstBlock();
627 while (block.isValid()) {
628 if (block.blockFormat().marker() != QTextBlockFormat::MarkerType::NoMarker) {
629 QRectF blockBr = blockBoundingRect(block);
630 QTextBlockFormat blockFmt = block.blockFormat();
631 QFontMetrics fm(block.charFormat().font());
632 qreal totalIndent = blockFmt.indent() + blockFmt.leftMargin() + blockFmt.textIndent();
633 if (block.textList())
634 totalIndent += block.textList()->format().indent() * 40;
635 QRectF adjustedBr = blockBr.adjusted(xp1: totalIndent - fm.height(), yp1: 0, xp2: totalIndent - blockBr.width(), yp2: fm.height() - blockBr.height());
636 if (adjustedBr.contains(p: pos)) {
637 //qDebug() << "hit block" << block.text() << blockBr << adjustedBr << "marker" << block.blockFormat().marker()
638 // << "font" << block.charFormat().font() << "adj" << lineHeight << totalIndent;
639 if (block.blockFormat().hasProperty(propertyId: QTextFormat::BlockMarker))
640 return block;
641 }
642 }
643 block = block.next();
644 }
645 return QTextBlock();
646}
647
648/*!
649 \fn QRectF QAbstractTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
650
651 Returns the bounding rectangle of \a frame.
652*/
653
654/*!
655 \fn QRectF QAbstractTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
656
657 Returns the bounding rectangle of \a block.
658*/
659
660/*!
661 Sets the paint device used for rendering the document's layout to the given
662 \a device.
663
664 \sa paintDevice()
665*/
666void QAbstractTextDocumentLayout::setPaintDevice(QPaintDevice *device)
667{
668 Q_D(QAbstractTextDocumentLayout);
669 d->paintDevice = device;
670}
671
672/*!
673 Returns the paint device used to render the document's layout.
674
675 \sa setPaintDevice()
676*/
677QPaintDevice *QAbstractTextDocumentLayout::paintDevice() const
678{
679 Q_D(const QAbstractTextDocumentLayout);
680 return d->paintDevice;
681}
682
683QT_END_NAMESPACE
684
685#include "moc_qabstracttextdocumentlayout.cpp"
686

source code of qtbase/src/gui/text/qabstracttextdocumentlayout.cpp