source: trunk/src/gui/text/qtextdocument.cpp@ 890

Last change on this file since 890 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 92.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtextdocument.h"
43#include <qtextformat.h>
44#include "qtextdocumentlayout_p.h"
45#include "qtextdocumentfragment.h"
46#include "qtextdocumentfragment_p.h"
47#include "qtexttable.h"
48#include "qtextlist.h"
49#include <qdebug.h>
50#include <qregexp.h>
51#include <qvarlengtharray.h>
52#include <qtextcodec.h>
53#include <qthread.h>
54#include "qtexthtmlparser_p.h"
55#include "qpainter.h"
56#include "qprinter.h"
57#include "qtextedit.h"
58#include <qfile.h>
59#include <qfileinfo.h>
60#include <qdir.h>
61#include <qapplication.h>
62#include "qtextcontrol_p.h"
63#include "private/qtextedit_p.h"
64#include "private/qdataurl_p.h"
65
66#include "qtextdocument_p.h"
67#include <private/qprinter_p.h>
68#include <private/qabstracttextdocumentlayout_p.h>
69
70#include <limits.h>
71
72QT_BEGIN_NAMESPACE
73
74Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n);
75
76/*!
77 Returns true if the string \a text is likely to be rich text;
78 otherwise returns false.
79
80 This function uses a fast and therefore simple heuristic. It
81 mainly checks whether there is something that looks like a tag
82 before the first line break. Although the result may be correct
83 for common cases, there is no guarantee.
84
85 This function is defined in the \c <QTextDocument> header file.
86*/
87bool Qt::mightBeRichText(const QString& text)
88{
89 if (text.isEmpty())
90 return false;
91 int start = 0;
92
93 while (start < text.length() && text.at(start).isSpace())
94 ++start;
95
96 // skip a leading <?xml ... ?> as for example with xhtml
97 if (text.mid(start, 5) == QLatin1String("<?xml")) {
98 while (start < text.length()) {
99 if (text.at(start) == QLatin1Char('?')
100 && start + 2 < text.length()
101 && text.at(start + 1) == QLatin1Char('>')) {
102 start += 2;
103 break;
104 }
105 ++start;
106 }
107
108 while (start < text.length() && text.at(start).isSpace())
109 ++start;
110 }
111
112 if (text.mid(start, 5).toLower() == QLatin1String("<!doc"))
113 return true;
114 int open = start;
115 while (open < text.length() && text.at(open) != QLatin1Char('<')
116 && text.at(open) != QLatin1Char('\n')) {
117 if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;"))
118 return true; // support desperate attempt of user to see <...>
119 ++open;
120 }
121 if (open < text.length() && text.at(open) == QLatin1Char('<')) {
122 const int close = text.indexOf(QLatin1Char('>'), open);
123 if (close > -1) {
124 QString tag;
125 for (int i = open+1; i < close; ++i) {
126 if (text[i].isDigit() || text[i].isLetter())
127 tag += text[i];
128 else if (!tag.isEmpty() && text[i].isSpace())
129 break;
130 else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close)
131 break;
132 else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!')))
133 return false; // that's not a tag
134 }
135#ifndef QT_NO_TEXTHTMLPARSER
136 return QTextHtmlParser::lookupElement(tag.toLower()) != -1;
137#else
138 return false;
139#endif // QT_NO_TEXTHTMLPARSER
140 }
141 }
142 return false;
143}
144
145/*!
146 Converts the plain text string \a plain to a HTML string with
147 HTML metacharacters \c{<}, \c{>}, \c{&}, and \c{"} replaced by HTML
148 entities.
149
150 Example:
151
152 \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0
153
154 This function is defined in the \c <QTextDocument> header file.
155
156 \sa convertFromPlainText(), mightBeRichText()
157*/
158QString Qt::escape(const QString& plain)
159{
160 QString rich;
161 rich.reserve(int(plain.length() * 1.1));
162 for (int i = 0; i < plain.length(); ++i) {
163 if (plain.at(i) == QLatin1Char('<'))
164 rich += QLatin1String("&lt;");
165 else if (plain.at(i) == QLatin1Char('>'))
166 rich += QLatin1String("&gt;");
167 else if (plain.at(i) == QLatin1Char('&'))
168 rich += QLatin1String("&amp;");
169 else if (plain.at(i) == QLatin1Char('"'))
170 rich += QLatin1String("&quot;");
171 else
172 rich += plain.at(i);
173 }
174 return rich;
175}
176
177/*!
178 \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode)
179
180 Converts the plain text string \a plain to an HTML-formatted
181 paragraph while preserving most of its look.
182
183 \a mode defines how whitespace is handled.
184
185 This function is defined in the \c <QTextDocument> header file.
186
187 \sa escape(), mightBeRichText()
188*/
189QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
190{
191 int col = 0;
192 QString rich;
193 rich += QLatin1String("<p>");
194 for (int i = 0; i < plain.length(); ++i) {
195 if (plain[i] == QLatin1Char('\n')){
196 int c = 1;
197 while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) {
198 i++;
199 c++;
200 }
201 if (c == 1)
202 rich += QLatin1String("<br>\n");
203 else {
204 rich += QLatin1String("</p>\n");
205 while (--c > 1)
206 rich += QLatin1String("<br>\n");
207 rich += QLatin1String("<p>");
208 }
209 col = 0;
210 } else {
211 if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){
212 rich += QChar(0x00a0U);
213 ++col;
214 while (col % 8) {
215 rich += QChar(0x00a0U);
216 ++col;
217 }
218 }
219 else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
220 rich += QChar(0x00a0U);
221 else if (plain[i] == QLatin1Char('<'))
222 rich += QLatin1String("&lt;");
223 else if (plain[i] == QLatin1Char('>'))
224 rich += QLatin1String("&gt;");
225 else if (plain[i] == QLatin1Char('&'))
226 rich += QLatin1String("&amp;");
227 else
228 rich += plain[i];
229 ++col;
230 }
231 }
232 if (col != 0)
233 rich += QLatin1String("</p>");
234 return rich;
235}
236
237#ifndef QT_NO_TEXTCODEC
238/*!
239 \internal
240
241 This function is defined in the \c <QTextDocument> header file.
242*/
243QTextCodec *Qt::codecForHtml(const QByteArray &ba)
244{
245 return QTextCodec::codecForHtml(ba);
246}
247#endif
248
249/*!
250 \class QTextDocument
251 \reentrant
252
253 \brief The QTextDocument class holds formatted text that can be
254 viewed and edited using a QTextEdit.
255
256 \ingroup richtext-processing
257
258
259 QTextDocument is a container for structured rich text documents, providing
260 support for styled text and various types of document elements, such as
261 lists, tables, frames, and images.
262 They can be created for use in a QTextEdit, or used independently.
263
264 Each document element is described by an associated format object. Each
265 format object is treated as a unique object by QTextDocuments, and can be
266 passed to objectForFormat() to obtain the document element that it is
267 applied to.
268
269 A QTextDocument can be edited programmatically using a QTextCursor, and
270 its contents can be examined by traversing the document structure. The
271 entire document structure is stored as a hierarchy of document elements
272 beneath the root frame, found with the rootFrame() function. Alternatively,
273 if you just want to iterate over the textual contents of the document you
274 can use begin(), end(), and findBlock() to retrieve text blocks that you
275 can examine and iterate over.
276
277 The layout of a document is determined by the documentLayout();
278 you can create your own QAbstractTextDocumentLayout subclass and
279 set it using setDocumentLayout() if you want to use your own
280 layout logic. The document's title and other meta-information can be
281 obtained by calling the metaInformation() function. For documents that
282 are exposed to users through the QTextEdit class, the document title
283 is also available via the QTextEdit::documentTitle() function.
284
285 The toPlainText() and toHtml() convenience functions allow you to retrieve the
286 contents of the document as plain text and HTML.
287 The document's text can be searched using the find() functions.
288
289 Undo/redo of operations performed on the document can be controlled using
290 the setUndoRedoEnabled() function. The undo/redo system can be controlled
291 by an editor widget through the undo() and redo() slots; the document also
292 provides contentsChanged(), undoAvailable(), and redoAvailable() signals
293 that inform connected editor widgets about the state of the undo/redo
294 system. The following are the undo/redo operations of a QTextDocument:
295
296 \list
297 \o Insertion or removal of characters. A sequence of insertions or removals
298 within the same text block are regarded as a single undo/redo operation.
299 \o Insertion or removal of text blocks. Sequences of insertion or removals
300 in a single operation (e.g., by selecting and then deleting text) are
301 regarded as a single undo/redo operation.
302 \o Text character format changes.
303 \o Text block format changes.
304 \o Text block group format changes.
305 \endlist
306
307 \sa QTextCursor, QTextEdit, \link richtext.html Rich Text Processing\endlink , {Text Object Example}
308*/
309
310/*!
311 \property QTextDocument::defaultFont
312 \brief the default font used to display the document's text
313*/
314
315/*!
316 \property QTextDocument::defaultTextOption
317 \brief the default text option will be set on all \l{QTextLayout}s in the document.
318
319 When \l{QTextBlock}s are created, the defaultTextOption is set on their
320 QTextLayout. This allows setting global properties for the document such as the
321 default word wrap mode.
322 */
323
324/*!
325 Constructs an empty QTextDocument with the given \a parent.
326*/
327QTextDocument::QTextDocument(QObject *parent)
328 : QObject(*new QTextDocumentPrivate, parent)
329{
330 Q_D(QTextDocument);
331 d->init();
332}
333
334/*!
335 Constructs a QTextDocument containing the plain (unformatted) \a text
336 specified, and with the given \a parent.
337*/
338QTextDocument::QTextDocument(const QString &text, QObject *parent)
339 : QObject(*new QTextDocumentPrivate, parent)
340{
341 Q_D(QTextDocument);
342 d->init();
343 QTextCursor(this).insertText(text);
344}
345
346/*!
347 \internal
348*/
349QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent)
350 : QObject(dd, parent)
351{
352 Q_D(QTextDocument);
353 d->init();
354}
355
356/*!
357 Destroys the document.
358*/
359QTextDocument::~QTextDocument()
360{
361}
362
363
364/*!
365 Creates a new QTextDocument that is a copy of this text document. \a
366 parent is the parent of the returned text document.
367*/
368QTextDocument *QTextDocument::clone(QObject *parent) const
369{
370 Q_D(const QTextDocument);
371 QTextDocument *doc = new QTextDocument(parent);
372 QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
373 doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat());
374 QTextDocumentPrivate *priv = doc->d_func();
375 priv->title = d->title;
376 priv->url = d->url;
377 priv->pageSize = d->pageSize;
378 priv->indentWidth = d->indentWidth;
379 priv->defaultTextOption = d->defaultTextOption;
380 priv->setDefaultFont(d->defaultFont());
381 priv->resources = d->resources;
382 priv->cachedResources.clear();
383#ifndef QT_NO_CSSPARSER
384 priv->defaultStyleSheet = d->defaultStyleSheet;
385 priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet;
386#endif
387 return doc;
388}
389
390/*!
391 Returns true if the document is empty; otherwise returns false.
392*/
393bool QTextDocument::isEmpty() const
394{
395 Q_D(const QTextDocument);
396 /* because if we're empty we still have one single paragraph as
397 * one single fragment */
398 return d->length() <= 1;
399}
400
401/*!
402 Clears the document.
403*/
404void QTextDocument::clear()
405{
406 Q_D(QTextDocument);
407 d->clear();
408 d->resources.clear();
409}
410
411/*!
412 \since 4.2
413
414 Undoes the last editing operation on the document if undo is
415 available. The provided \a cursor is positioned at the end of the
416 location where the edition operation was undone.
417
418 See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
419 documentation for details.
420
421 \sa undoAvailable(), isUndoRedoEnabled()
422*/
423void QTextDocument::undo(QTextCursor *cursor)
424{
425 Q_D(QTextDocument);
426 const int pos = d->undoRedo(true);
427 if (cursor && pos >= 0) {
428 *cursor = QTextCursor(this);
429 cursor->setPosition(pos);
430 }
431}
432
433/*!
434 \since 4.2
435 Redoes the last editing operation on the document if \link
436 QTextDocument::isRedoAvailable() redo is available\endlink.
437
438 The provided \a cursor is positioned at the end of the location where
439 the edition operation was redone.
440*/
441void QTextDocument::redo(QTextCursor *cursor)
442{
443 Q_D(QTextDocument);
444 const int pos = d->undoRedo(false);
445 if (cursor && pos >= 0) {
446 *cursor = QTextCursor(this);
447 cursor->setPosition(pos);
448 }
449}
450
451/*! \enum QTextDocument::Stacks
452
453 \value UndoStack The undo stack.
454 \value RedoStack The redo stack.
455 \value UndoAndRedoStacks Both the undo and redo stacks.
456*/
457
458/*!
459 \since 4.7
460 Clears the stacks specified by \a stacksToClear.
461
462 This method clears any commands on the undo stack, the redo stack,
463 or both (the default). If commands are cleared, the appropriate
464 signals are emitted, QTextDocument::undoAvailable() or
465 QTextDocument::redoAvailable().
466
467 \sa QTextDocument::undoAvailable() QTextDocument::redoAvailable()
468*/
469void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear)
470{
471 Q_D(QTextDocument);
472 d->clearUndoRedoStacks(stacksToClear, true);
473}
474
475/*!
476 \overload
477
478*/
479void QTextDocument::undo()
480{
481 Q_D(QTextDocument);
482 d->undoRedo(true);
483}
484
485/*!
486 \overload
487 Redoes the last editing operation on the document if \link
488 QTextDocument::isRedoAvailable() redo is available\endlink.
489*/
490void QTextDocument::redo()
491{
492 Q_D(QTextDocument);
493 d->undoRedo(false);
494}
495
496/*!
497 \internal
498
499 Appends a custom undo \a item to the undo stack.
500*/
501void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
502{
503 Q_D(QTextDocument);
504 d->appendUndoItem(item);
505}
506
507/*!
508 \property QTextDocument::undoRedoEnabled
509 \brief whether undo/redo are enabled for this document
510
511 This defaults to true. If disabled, the undo stack is cleared and
512 no items will be added to it.
513*/
514void QTextDocument::setUndoRedoEnabled(bool enable)
515{
516 Q_D(QTextDocument);
517 d->enableUndoRedo(enable);
518}
519
520bool QTextDocument::isUndoRedoEnabled() const
521{
522 Q_D(const QTextDocument);
523 return d->isUndoRedoEnabled();
524}
525
526/*!
527 \property QTextDocument::maximumBlockCount
528 \since 4.2
529 \brief Specifies the limit for blocks in the document.
530
531 Specifies the maximum number of blocks the document may have. If there are
532 more blocks in the document that specified with this property blocks are removed
533 from the beginning of the document.
534
535 A negative or zero value specifies that the document may contain an unlimited
536 amount of blocks.
537
538 The default value is 0.
539
540 Note that setting this property will apply the limit immediately to the document
541 contents.
542
543 Setting this property also disables the undo redo history.
544
545 This property is undefined in documents with tables or frames.
546*/
547int QTextDocument::maximumBlockCount() const
548{
549 Q_D(const QTextDocument);
550 return d->maximumBlockCount;
551}
552
553void QTextDocument::setMaximumBlockCount(int maximum)
554{
555 Q_D(QTextDocument);
556 d->maximumBlockCount = maximum;
557 d->ensureMaximumBlockCount();
558 setUndoRedoEnabled(false);
559}
560
561/*!
562 \since 4.3
563
564 The default text option is used on all QTextLayout objects in the document.
565 This allows setting global properties for the document such as the default
566 word wrap mode.
567*/
568QTextOption QTextDocument::defaultTextOption() const
569{
570 Q_D(const QTextDocument);
571 return d->defaultTextOption;
572}
573
574/*!
575 \since 4.3
576
577 Sets the default text option.
578*/
579void QTextDocument::setDefaultTextOption(const QTextOption &option)
580{
581 Q_D(QTextDocument);
582 d->defaultTextOption = option;
583 if (d->lout)
584 d->lout->documentChanged(0, 0, d->length());
585}
586
587/*!
588 \fn void QTextDocument::markContentsDirty(int position, int length)
589
590 Marks the contents specified by the given \a position and \a length
591 as "dirty", informing the document that it needs to be laid out
592 again.
593*/
594void QTextDocument::markContentsDirty(int from, int length)
595{
596 Q_D(QTextDocument);
597 d->documentChange(from, length);
598 if (!d->inContentsChange) {
599 if (d->lout) {
600 d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength);
601 d->docChangeFrom = -1;
602 }
603 }
604}
605
606/*!
607 \property QTextDocument::useDesignMetrics
608 \since 4.1
609 \brief whether the document uses design metrics of fonts to improve the accuracy of text layout
610
611 If this property is set to true, the layout will use design metrics.
612 Otherwise, the metrics of the paint device as set on
613 QAbstractTextDocumentLayout::setPaintDevice() will be used.
614
615 Using design metrics makes a layout have a width that is no longer dependent on hinting
616 and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width
617 scales much more linearly based on paintdevice metrics than it would otherwise.
618
619 By default, this property is false.
620*/
621
622void QTextDocument::setUseDesignMetrics(bool b)
623{
624 Q_D(QTextDocument);
625 if (b == d->defaultTextOption.useDesignMetrics())
626 return;
627 d->defaultTextOption.setUseDesignMetrics(b);
628 if (d->lout)
629 d->lout->documentChanged(0, 0, d->length());
630}
631
632bool QTextDocument::useDesignMetrics() const
633{
634 Q_D(const QTextDocument);
635 return d->defaultTextOption.useDesignMetrics();
636}
637
638/*!
639 \since 4.2
640
641 Draws the content of the document with painter \a p, clipped to \a rect.
642 If \a rect is a null rectangle (default) then the document is painted unclipped.
643*/
644void QTextDocument::drawContents(QPainter *p, const QRectF &rect)
645{
646 p->save();
647 QAbstractTextDocumentLayout::PaintContext ctx;
648 if (rect.isValid()) {
649 p->setClipRect(rect);
650 ctx.clip = rect;
651 }
652 documentLayout()->draw(p, ctx);
653 p->restore();
654}
655
656/*!
657 \property QTextDocument::textWidth
658 \since 4.2
659
660 The text width specifies the preferred width for text in the document. If
661 the text (or content in general) is wider than the specified with it is broken
662 into multiple lines and grows vertically. If the text cannot be broken into multiple
663 lines to fit into the specified text width it will be larger and the size() and the
664 idealWidth() property will reflect that.
665
666 If the text width is set to -1 then the text will not be broken into multiple lines
667 unless it is enforced through an explicit line break or a new paragraph.
668
669 The default value is -1.
670
671 Setting the text width will also set the page height to -1, causing the document to
672 grow or shrink vertically in a continuous way. If you want the document layout to break
673 the text into multiple pages then you have to set the pageSize property instead.
674
675 \sa size(), idealWidth(), pageSize()
676*/
677void QTextDocument::setTextWidth(qreal width)
678{
679 Q_D(QTextDocument);
680 QSizeF sz = d->pageSize;
681 sz.setWidth(width);
682 sz.setHeight(-1);
683 setPageSize(sz);
684}
685
686qreal QTextDocument::textWidth() const
687{
688 Q_D(const QTextDocument);
689 return d->pageSize.width();
690}
691
692/*!
693 \since 4.2
694
695 Returns the ideal width of the text document. The ideal width is the actually used width
696 of the document without optional alignments taken into account. It is always <= size().width().
697
698 \sa adjustSize(), textWidth
699*/
700qreal QTextDocument::idealWidth() const
701{
702 if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout()))
703 return lout->idealWidth();
704 return textWidth();
705}
706
707/*!
708 \property QTextDocument::documentMargin
709 \since 4.5
710
711 The margin around the document. The default is 4.
712*/
713qreal QTextDocument::documentMargin() const
714{
715 Q_D(const QTextDocument);
716 return d->documentMargin;
717}
718
719void QTextDocument::setDocumentMargin(qreal margin)
720{
721 Q_D(QTextDocument);
722 if (d->documentMargin != margin) {
723 d->documentMargin = margin;
724
725 QTextFrame* root = rootFrame();
726 QTextFrameFormat format = root->frameFormat();
727 format.setMargin(margin);
728 root->setFrameFormat(format);
729
730 if (d->lout)
731 d->lout->documentChanged(0, 0, d->length());
732 }
733}
734
735
736/*!
737 \property QTextDocument::indentWidth
738 \since 4.4
739
740 Returns the width used for text list and text block indenting.
741
742 The indent properties of QTextListFormat and QTextBlockFormat specify
743 multiples of this value. The default indent width is 40.
744*/
745qreal QTextDocument::indentWidth() const
746{
747 Q_D(const QTextDocument);
748 return d->indentWidth;
749}
750
751
752/*!
753 \since 4.4
754
755 Sets the \a width used for text list and text block indenting.
756
757 The indent properties of QTextListFormat and QTextBlockFormat specify
758 multiples of this value. The default indent width is 40 .
759
760 \sa indentWidth()
761*/
762void QTextDocument::setIndentWidth(qreal width)
763{
764 Q_D(QTextDocument);
765 if (d->indentWidth != width) {
766 d->indentWidth = width;
767 if (d->lout)
768 d->lout->documentChanged(0, 0, d->length());
769 }
770}
771
772
773
774
775/*!
776 \since 4.2
777
778 Adjusts the document to a reasonable size.
779
780 \sa idealWidth(), textWidth, size
781*/
782void QTextDocument::adjustSize()
783{
784 // Pull this private function in from qglobal.cpp
785 QFont f = defaultFont();
786 QFontMetrics fm(f);
787 int mw = fm.width(QLatin1Char('x')) * 80;
788 int w = mw;
789 setTextWidth(w);
790 QSizeF size = documentLayout()->documentSize();
791 if (size.width() != 0) {
792 w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3));
793 setTextWidth(qMin(w, mw));
794
795 size = documentLayout()->documentSize();
796 if (w*3 < 5*size.height()) {
797 w = qt_int_sqrt((uint)(2 * size.height() * size.width()));
798 setTextWidth(qMin(w, mw));
799 }
800 }
801 setTextWidth(idealWidth());
802}
803
804/*!
805 \property QTextDocument::size
806 \since 4.2
807
808 Returns the actual size of the document.
809 This is equivalent to documentLayout()->documentSize();
810
811 The size of the document can be changed either by setting
812 a text width or setting an entire page size.
813
814 Note that the width is always >= pageSize().width().
815
816 By default, for a newly-created, empty document, this property contains
817 a configuration-dependent size.
818
819 \sa setTextWidth(), setPageSize(), idealWidth()
820*/
821QSizeF QTextDocument::size() const
822{
823 return documentLayout()->documentSize();
824}
825
826/*!
827 \property QTextDocument::blockCount
828 \since 4.2
829
830 Returns the number of text blocks in the document.
831
832 The value of this property is undefined in documents with tables or frames.
833
834 By default, if defined, this property contains a value of 1.
835 \sa lineCount(), characterCount()
836*/
837int QTextDocument::blockCount() const
838{
839 Q_D(const QTextDocument);
840 return d->blockMap().numNodes();
841}
842
843
844/*!
845 \since 4.5
846
847 Returns the number of lines of this document (if the layout supports
848 this). Otherwise, this is identical to the number of blocks.
849
850 \sa blockCount(), characterCount()
851 */
852int QTextDocument::lineCount() const
853{
854 Q_D(const QTextDocument);
855 return d->blockMap().length(2);
856}
857
858/*!
859 \since 4.5
860
861 Returns the number of characters of this document.
862
863 \sa blockCount(), characterAt()
864 */
865int QTextDocument::characterCount() const
866{
867 Q_D(const QTextDocument);
868 return d->length();
869}
870
871/*!
872 \since 4.5
873
874 Returns the character at position \a pos, or a null character if the
875 position is out of range.
876
877 \sa characterCount()
878 */
879QChar QTextDocument::characterAt(int pos) const
880{
881 Q_D(const QTextDocument);
882 if (pos < 0 || pos >= d->length())
883 return QChar();
884 QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos);
885 const QTextFragmentData * const frag = fragIt.value();
886 const int offsetInFragment = qMax(0, pos - fragIt.position());
887 return d->text.at(frag->stringPosition + offsetInFragment);
888}
889
890
891/*!
892 \property QTextDocument::defaultStyleSheet
893 \since 4.2
894
895 The default style sheet is applied to all newly HTML formatted text that is
896 inserted into the document, for example using setHtml() or QTextCursor::insertHtml().
897
898 The style sheet needs to be compliant to CSS 2.1 syntax.
899
900 \bold{Note:} Changing the default style sheet does not have any effect to the existing content
901 of the document.
902
903 \sa {Supported HTML Subset}
904*/
905
906#ifndef QT_NO_CSSPARSER
907void QTextDocument::setDefaultStyleSheet(const QString &sheet)
908{
909 Q_D(QTextDocument);
910 d->defaultStyleSheet = sheet;
911 QCss::Parser parser(sheet);
912 d->parsedDefaultStyleSheet = QCss::StyleSheet();
913 d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent;
914 parser.parse(&d->parsedDefaultStyleSheet);
915}
916
917QString QTextDocument::defaultStyleSheet() const
918{
919 Q_D(const QTextDocument);
920 return d->defaultStyleSheet;
921}
922#endif // QT_NO_CSSPARSER
923
924/*!
925 \fn void QTextDocument::contentsChanged()
926
927 This signal is emitted whenever the document's content changes; for
928 example, when text is inserted or deleted, or when formatting is applied.
929
930 \sa contentsChange()
931*/
932
933/*!
934 \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
935
936 This signal is emitted whenever the document's content changes; for
937 example, when text is inserted or deleted, or when formatting is applied.
938
939 Information is provided about the \a position of the character in the
940 document where the change occurred, the number of characters removed
941 (\a charsRemoved), and the number of characters added (\a charsAdded).
942
943 The signal is emitted before the document's layout manager is notified
944 about the change. This hook allows you to implement syntax highlighting
945 for the document.
946
947 \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
948*/
949
950
951/*!
952 \fn QTextDocument::undoAvailable(bool available);
953
954 This signal is emitted whenever undo operations become available
955 (\a available is true) or unavailable (\a available is false).
956
957 See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
958 documentation for details.
959
960 \sa undo(), isUndoRedoEnabled()
961*/
962
963/*!
964 \fn QTextDocument::redoAvailable(bool available);
965
966 This signal is emitted whenever redo operations become available
967 (\a available is true) or unavailable (\a available is false).
968*/
969
970/*!
971 \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
972
973 This signal is emitted whenever the position of a cursor changed
974 due to an editing operation. The cursor that changed is passed in
975 \a cursor. If you need a signal when the cursor is moved with the
976 arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in
977 QTextEdit.
978*/
979
980/*!
981 \fn QTextDocument::blockCountChanged(int newBlockCount);
982 \since 4.3
983
984 This signal is emitted when the total number of text blocks in the
985 document changes. The value passed in \a newBlockCount is the new
986 total.
987*/
988
989/*!
990 \fn QTextDocument::documentLayoutChanged();
991 \since 4.4
992
993 This signal is emitted when a new document layout is set.
994
995 \sa setDocumentLayout()
996
997*/
998
999
1000/*!
1001 Returns true if undo is available; otherwise returns false.
1002
1003 \sa isRedoAvailable(), availableUndoSteps()
1004*/
1005bool QTextDocument::isUndoAvailable() const
1006{
1007 Q_D(const QTextDocument);
1008 return d->isUndoAvailable();
1009}
1010
1011/*!
1012 Returns true if redo is available; otherwise returns false.
1013
1014 \sa isUndoAvailable(), availableRedoSteps()
1015*/
1016bool QTextDocument::isRedoAvailable() const
1017{
1018 Q_D(const QTextDocument);
1019 return d->isRedoAvailable();
1020}
1021
1022/*! \since 4.6
1023
1024 Returns the number of available undo steps.
1025
1026 \sa isUndoAvailable()
1027*/
1028int QTextDocument::availableUndoSteps() const
1029{
1030 Q_D(const QTextDocument);
1031 return d->availableUndoSteps();
1032}
1033
1034/*! \since 4.6
1035
1036 Returns the number of available redo steps.
1037
1038 \sa isRedoAvailable()
1039*/
1040int QTextDocument::availableRedoSteps() const
1041{
1042 Q_D(const QTextDocument);
1043 return d->availableRedoSteps();
1044}
1045
1046/*! \since 4.4
1047
1048 Returns the document's revision (if undo is enabled).
1049
1050 The revision is guaranteed to increase when a document that is not
1051 modified is edited.
1052
1053 \sa QTextBlock::revision(), isModified()
1054 */
1055int QTextDocument::revision() const
1056{
1057 Q_D(const QTextDocument);
1058 return d->revision;
1059}
1060
1061
1062
1063/*!
1064 Sets the document to use the given \a layout. The previous layout
1065 is deleted.
1066
1067 \sa documentLayoutChanged()
1068*/
1069void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
1070{
1071 Q_D(QTextDocument);
1072 d->setLayout(layout);
1073}
1074
1075/*!
1076 Returns the document layout for this document.
1077*/
1078QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
1079{
1080 Q_D(const QTextDocument);
1081 if (!d->lout) {
1082 QTextDocument *that = const_cast<QTextDocument *>(this);
1083 that->d_func()->setLayout(new QTextDocumentLayout(that));
1084 }
1085 return d->lout;
1086}
1087
1088
1089/*!
1090 Returns meta information about the document of the type specified by
1091 \a info.
1092
1093 \sa setMetaInformation()
1094*/
1095QString QTextDocument::metaInformation(MetaInformation info) const
1096{
1097 Q_D(const QTextDocument);
1098 switch (info) {
1099 case DocumentTitle:
1100 return d->title;
1101 case DocumentUrl:
1102 return d->url;
1103 }
1104 return QString();
1105}
1106
1107/*!
1108 Sets the document's meta information of the type specified by \a info
1109 to the given \a string.
1110
1111 \sa metaInformation()
1112*/
1113void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
1114{
1115 Q_D(QTextDocument);
1116 switch (info) {
1117 case DocumentTitle:
1118 d->title = string;
1119 break;
1120 case DocumentUrl:
1121 d->url = string;
1122 break;
1123 }
1124}
1125
1126/*!
1127 Returns the plain text contained in the document. If you want
1128 formatting information use a QTextCursor instead.
1129
1130 \sa toHtml()
1131*/
1132QString QTextDocument::toPlainText() const
1133{
1134 Q_D(const QTextDocument);
1135 QString txt = d->plainText();
1136
1137 QChar *uc = txt.data();
1138 QChar *e = uc + txt.size();
1139
1140 for (; uc != e; ++uc) {
1141 switch (uc->unicode()) {
1142 case 0xfdd0: // QTextBeginningOfFrame
1143 case 0xfdd1: // QTextEndOfFrame
1144 case QChar::ParagraphSeparator:
1145 case QChar::LineSeparator:
1146 *uc = QLatin1Char('\n');
1147 break;
1148 case QChar::Nbsp:
1149 *uc = QLatin1Char(' ');
1150 break;
1151 default:
1152 ;
1153 }
1154 }
1155 return txt;
1156}
1157
1158/*!
1159 Replaces the entire contents of the document with the given plain
1160 \a text.
1161
1162 \sa setHtml()
1163*/
1164void QTextDocument::setPlainText(const QString &text)
1165{
1166 Q_D(QTextDocument);
1167 bool previousState = d->isUndoRedoEnabled();
1168 d->enableUndoRedo(false);
1169 d->beginEditBlock();
1170 d->clear();
1171 QTextCursor(this).insertText(text);
1172 d->endEditBlock();
1173 d->enableUndoRedo(previousState);
1174}
1175
1176/*!
1177 Replaces the entire contents of the document with the given
1178 HTML-formatted text in the \a html string.
1179
1180 The HTML formatting is respected as much as possible; for example,
1181 "<b>bold</b> text" will produce text where the first word has a font
1182 weight that gives it a bold appearance: "\bold{bold} text".
1183
1184 \note It is the responsibility of the caller to make sure that the
1185 text is correctly decoded when a QString containing HTML is created
1186 and passed to setHtml().
1187
1188 \sa setPlainText(), {Supported HTML Subset}
1189*/
1190
1191#ifndef QT_NO_TEXTHTMLPARSER
1192
1193void QTextDocument::setHtml(const QString &html)
1194{
1195 Q_D(QTextDocument);
1196 bool previousState = d->isUndoRedoEnabled();
1197 d->enableUndoRedo(false);
1198 d->beginEditBlock();
1199 d->clear();
1200 QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
1201 d->endEditBlock();
1202 d->enableUndoRedo(previousState);
1203}
1204
1205#endif // QT_NO_TEXTHTMLPARSER
1206
1207/*!
1208 \enum QTextDocument::FindFlag
1209
1210 This enum describes the options available to QTextDocument's find function. The options
1211 can be OR-ed together from the following list:
1212
1213 \value FindBackward Search backwards instead of forwards.
1214 \value FindCaseSensitively By default find works case insensitive. Specifying this option
1215 changes the behaviour to a case sensitive find operation.
1216 \value FindWholeWords Makes find match only complete words.
1217*/
1218
1219/*!
1220 \enum QTextDocument::MetaInformation
1221
1222 This enum describes the different types of meta information that can be
1223 added to a document.
1224
1225 \value DocumentTitle The title of the document.
1226 \value DocumentUrl The url of the document. The loadResource() function uses
1227 this url as the base when loading relative resources.
1228
1229 \sa metaInformation(), setMetaInformation()
1230*/
1231
1232/*!
1233 \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const
1234
1235 \overload
1236
1237 Finds the next occurrence of the string, \a subString, in the document.
1238 The search starts at the given \a position, and proceeds forwards
1239 through the document unless specified otherwise in the search options.
1240 The \a options control the type of search performed.
1241
1242 Returns a cursor with the match selected if \a subString
1243 was found; otherwise returns a null cursor.
1244
1245 If the \a position is 0 (the default) the search begins from the beginning
1246 of the document; otherwise it begins at the specified position.
1247*/
1248QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const
1249{
1250 QRegExp expr(subString);
1251 expr.setPatternSyntax(QRegExp::FixedString);
1252 expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
1253
1254 return find(expr, from, options);
1255}
1256
1257/*!
1258 \fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const
1259
1260 Finds the next occurrence of the string, \a subString, in the document.
1261 The search starts at the position of the given \a cursor, and proceeds
1262 forwards through the document unless specified otherwise in the search
1263 options. The \a options control the type of search performed.
1264
1265 Returns a cursor with the match selected if \a subString was found; otherwise
1266 returns a null cursor.
1267
1268 If the given \a cursor has a selection, the search begins after the
1269 selection; otherwise it begins at the cursor's position.
1270
1271 By default the search is case-sensitive, and can match text anywhere in the
1272 document.
1273*/
1274QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const
1275{
1276 int pos = 0;
1277 if (!from.isNull()) {
1278 if (options & QTextDocument::FindBackward)
1279 pos = from.selectionStart();
1280 else
1281 pos = from.selectionEnd();
1282 }
1283 QRegExp expr(subString);
1284 expr.setPatternSyntax(QRegExp::FixedString);
1285 expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
1286
1287 return find(expr, pos, options);
1288}
1289
1290
1291static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset,
1292 QTextDocument::FindFlags options, QTextCursor &cursor)
1293{
1294 const QRegExp expr(expression);
1295 QString text = block.text();
1296 text.replace(QChar::Nbsp, QLatin1Char(' '));
1297
1298 int idx = -1;
1299 while (offset >=0 && offset <= text.length()) {
1300 idx = (options & QTextDocument::FindBackward) ?
1301 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
1302 if (idx == -1)
1303 return false;
1304
1305 if (options & QTextDocument::FindWholeWords) {
1306 const int start = idx;
1307 const int end = start + expr.matchedLength();
1308 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1309 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1310 //if this is not a whole word, continue the search in the string
1311 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1312 idx = -1;
1313 continue;
1314 }
1315 }
1316 //we have a hit, return the cursor for that.
1317 break;
1318 }
1319 if (idx == -1)
1320 return false;
1321 cursor = QTextCursor(block.docHandle(), block.position() + idx);
1322 cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
1323 return true;
1324}
1325
1326/*!
1327 \fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const
1328
1329 \overload
1330
1331 Finds the next occurrence, matching the regular expression, \a expr, in the document.
1332 The search starts at the given \a position, and proceeds forwards
1333 through the document unless specified otherwise in the search options.
1334 The \a options control the type of search performed. The FindCaseSensitively
1335 option is ignored for this overload, use QRegExp::caseSensitivity instead.
1336
1337 Returns a cursor with the match selected if a match was found; otherwise
1338 returns a null cursor.
1339
1340 If the \a position is 0 (the default) the search begins from the beginning
1341 of the document; otherwise it begins at the specified position.
1342*/
1343QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const
1344{
1345 Q_D(const QTextDocument);
1346
1347 if (expr.isEmpty())
1348 return QTextCursor();
1349
1350 int pos = from;
1351 //the cursor is positioned between characters, so for a backward search
1352 //do not include the character given in the position.
1353 if (options & FindBackward) {
1354 --pos ;
1355 if(pos < 0)
1356 return QTextCursor();
1357 }
1358
1359 QTextCursor cursor;
1360 QTextBlock block = d->blocksFind(pos);
1361
1362 if (!(options & FindBackward)) {
1363 int blockOffset = qMax(0, pos - block.position());
1364 while (block.isValid()) {
1365 if (findInBlock(block, expr, blockOffset, options, cursor))
1366 return cursor;
1367 blockOffset = 0;
1368 block = block.next();
1369 }
1370 } else {
1371 int blockOffset = pos - block.position();
1372 while (block.isValid()) {
1373 if (findInBlock(block, expr, blockOffset, options, cursor))
1374 return cursor;
1375 block = block.previous();
1376 blockOffset = block.length() - 1;
1377 }
1378 }
1379
1380 return QTextCursor();
1381}
1382
1383/*!
1384 \fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const
1385
1386 Finds the next occurrence, matching the regular expression, \a expr, in the document.
1387 The search starts at the position of the given \a cursor, and proceeds
1388 forwards through the document unless specified otherwise in the search
1389 options. The \a options control the type of search performed. The FindCaseSensitively
1390 option is ignored for this overload, use QRegExp::caseSensitivity instead.
1391
1392 Returns a cursor with the match selected if a match was found; otherwise
1393 returns a null cursor.
1394
1395 If the given \a cursor has a selection, the search begins after the
1396 selection; otherwise it begins at the cursor's position.
1397
1398 By default the search is case-sensitive, and can match text anywhere in the
1399 document.
1400*/
1401QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const
1402{
1403 int pos = 0;
1404 if (!from.isNull()) {
1405 if (options & QTextDocument::FindBackward)
1406 pos = from.selectionStart();
1407 else
1408 pos = from.selectionEnd();
1409 }
1410 return find(expr, pos, options);
1411}
1412
1413
1414/*!
1415 \fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
1416
1417 Creates and returns a new document object (a QTextObject), based
1418 on the given \a format.
1419
1420 QTextObjects will always get created through this method, so you
1421 must reimplement it if you use custom text objects inside your document.
1422*/
1423QTextObject *QTextDocument::createObject(const QTextFormat &f)
1424{
1425 QTextObject *obj = 0;
1426 if (f.isListFormat())
1427 obj = new QTextList(this);
1428 else if (f.isTableFormat())
1429 obj = new QTextTable(this);
1430 else if (f.isFrameFormat())
1431 obj = new QTextFrame(this);
1432
1433 return obj;
1434}
1435
1436/*!
1437 \internal
1438
1439 Returns the frame that contains the text cursor position \a pos.
1440*/
1441QTextFrame *QTextDocument::frameAt(int pos) const
1442{
1443 Q_D(const QTextDocument);
1444 return d->frameAt(pos);
1445}
1446
1447/*!
1448 Returns the document's root frame.
1449*/
1450QTextFrame *QTextDocument::rootFrame() const
1451{
1452 Q_D(const QTextDocument);
1453 return d->rootFrame();
1454}
1455
1456/*!
1457 Returns the text object associated with the given \a objectIndex.
1458*/
1459QTextObject *QTextDocument::object(int objectIndex) const
1460{
1461 Q_D(const QTextDocument);
1462 return d->objectForIndex(objectIndex);
1463}
1464
1465/*!
1466 Returns the text object associated with the format \a f.
1467*/
1468QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
1469{
1470 Q_D(const QTextDocument);
1471 return d->objectForFormat(f);
1472}
1473
1474
1475/*!
1476 Returns the text block that contains the \a{pos}-th character.
1477*/
1478QTextBlock QTextDocument::findBlock(int pos) const
1479{
1480 Q_D(const QTextDocument);
1481 return QTextBlock(docHandle(), d->blockMap().findNode(pos));
1482}
1483
1484/*!
1485 \since 4.4
1486 Returns the text block with the specified \a blockNumber.
1487
1488 \sa QTextBlock::blockNumber()
1489*/
1490QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const
1491{
1492 Q_D(const QTextDocument);
1493 return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1));
1494}
1495
1496/*!
1497 \since 4.5
1498 Returns the text block that contains the specified \a lineNumber.
1499
1500 \sa QTextBlock::firstLineNumber()
1501*/
1502QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const
1503{
1504 Q_D(const QTextDocument);
1505 return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2));
1506}
1507
1508/*!
1509 Returns the document's first text block.
1510
1511 \sa firstBlock()
1512*/
1513QTextBlock QTextDocument::begin() const
1514{
1515 Q_D(const QTextDocument);
1516 return QTextBlock(docHandle(), d->blockMap().begin().n);
1517}
1518
1519/*!
1520 This function returns a block to test for the end of the document
1521 while iterating over it.
1522
1523 \snippet doc/src/snippets/textdocumentendsnippet.cpp 0
1524
1525 The block returned is invalid and represents the block after the
1526 last block in the document. You can use lastBlock() to retrieve the
1527 last valid block of the document.
1528
1529 \sa lastBlock()
1530*/
1531QTextBlock QTextDocument::end() const
1532{
1533 return QTextBlock(docHandle(), 0);
1534}
1535
1536/*!
1537 \since 4.4
1538 Returns the document's first text block.
1539*/
1540QTextBlock QTextDocument::firstBlock() const
1541{
1542 Q_D(const QTextDocument);
1543 return QTextBlock(docHandle(), d->blockMap().begin().n);
1544}
1545
1546/*!
1547 \since 4.4
1548 Returns the document's last (valid) text block.
1549*/
1550QTextBlock QTextDocument::lastBlock() const
1551{
1552 Q_D(const QTextDocument);
1553 return QTextBlock(docHandle(), d->blockMap().last().n);
1554}
1555
1556/*!
1557 \property QTextDocument::pageSize
1558 \brief the page size that should be used for laying out the document
1559
1560 By default, for a newly-created, empty document, this property contains
1561 an undefined size.
1562
1563 \sa modificationChanged()
1564*/
1565
1566void QTextDocument::setPageSize(const QSizeF &size)
1567{
1568 Q_D(QTextDocument);
1569 d->pageSize = size;
1570 if (d->lout)
1571 d->lout->documentChanged(0, 0, d->length());
1572}
1573
1574QSizeF QTextDocument::pageSize() const
1575{
1576 Q_D(const QTextDocument);
1577 return d->pageSize;
1578}
1579
1580/*!
1581 returns the number of pages in this document.
1582*/
1583int QTextDocument::pageCount() const
1584{
1585 return documentLayout()->pageCount();
1586}
1587
1588/*!
1589 Sets the default \a font to use in the document layout.
1590*/
1591void QTextDocument::setDefaultFont(const QFont &font)
1592{
1593 Q_D(QTextDocument);
1594 d->setDefaultFont(font);
1595 if (d->lout)
1596 d->lout->documentChanged(0, 0, d->length());
1597}
1598
1599/*!
1600 Returns the default font to be used in the document layout.
1601*/
1602QFont QTextDocument::defaultFont() const
1603{
1604 Q_D(const QTextDocument);
1605 return d->defaultFont();
1606}
1607
1608/*!
1609 \fn QTextDocument::modificationChanged(bool changed)
1610
1611 This signal is emitted whenever the content of the document
1612 changes in a way that affects the modification state. If \a
1613 changed is true, the document has been modified; otherwise it is
1614 false.
1615
1616 For example, calling setModified(false) on a document and then
1617 inserting text causes the signal to get emitted. If you undo that
1618 operation, causing the document to return to its original
1619 unmodified state, the signal will get emitted again.
1620*/
1621
1622/*!
1623 \property QTextDocument::modified
1624 \brief whether the document has been modified by the user
1625
1626 By default, this property is false.
1627
1628 \sa modificationChanged()
1629*/
1630
1631bool QTextDocument::isModified() const
1632{
1633 return docHandle()->isModified();
1634}
1635
1636void QTextDocument::setModified(bool m)
1637{
1638 docHandle()->setModified(m);
1639}
1640
1641#ifndef QT_NO_PRINTER
1642static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos)
1643{
1644 painter->save();
1645 painter->translate(body.left(), body.top() - (index - 1) * body.height());
1646 QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
1647
1648 QAbstractTextDocumentLayout *layout = doc->documentLayout();
1649 QAbstractTextDocumentLayout::PaintContext ctx;
1650
1651 painter->setClipRect(view);
1652 ctx.clip = view;
1653
1654 // don't use the system palette text as default text color, on HP/UX
1655 // for example that's white, and white text on white paper doesn't
1656 // look that nice
1657 ctx.palette.setColor(QPalette::Text, Qt::black);
1658
1659 layout->draw(painter, ctx);
1660
1661 if (!pageNumberPos.isNull()) {
1662 painter->setClipping(false);
1663 painter->setFont(QFont(doc->defaultFont()));
1664 const QString pageString = QString::number(index);
1665
1666 painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)),
1667 qRound(pageNumberPos.y() + view.top()),
1668 pageString);
1669 }
1670
1671 painter->restore();
1672}
1673
1674Q_GUI_EXPORT extern int qt_defaultDpi();
1675
1676/*!
1677 Prints the document to the given \a printer. The QPrinter must be
1678 set up before being used with this function.
1679
1680 This is only a convenience method to print the whole document to the printer.
1681
1682 If the document is already paginated through a specified height in the pageSize()
1683 property it is printed as-is.
1684
1685 If the document is not paginated, like for example a document used in a QTextEdit,
1686 then a temporary copy of the document is created and the copy is broken into
1687 multiple pages according to the size of the QPrinter's paperRect(). By default
1688 a 2 cm margin is set around the document contents. In addition the current page
1689 number is printed at the bottom of each page.
1690
1691 Note that QPrinter::Selection is not supported as print range with this function since
1692 the selection is a property of QTextCursor. If you have a QTextEdit associated with
1693 your QTextDocument then you can use QTextEdit's print() function because QTextEdit has
1694 access to the user's selection.
1695
1696 \sa QTextEdit::print()
1697*/
1698
1699void QTextDocument::print(QPrinter *printer) const
1700{
1701 Q_D(const QTextDocument);
1702
1703 if (!printer || !printer->isValid())
1704 return;
1705
1706 if (!d->title.isEmpty())
1707 printer->setDocName(d->title);
1708
1709 bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull()
1710 && d->pageSize.height() != INT_MAX;
1711
1712 if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins)
1713 printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter);
1714
1715 QPainter p(printer);
1716
1717 // Check that there is a valid device to print to.
1718 if (!p.isActive())
1719 return;
1720
1721 const QTextDocument *doc = this;
1722 QScopedPointer<QTextDocument> clonedDoc;
1723 (void)doc->documentLayout(); // make sure that there is a layout
1724
1725 QRectF body = QRectF(QPointF(0, 0), d->pageSize);
1726 QPointF pageNumberPos;
1727
1728 if (documentPaginated) {
1729 qreal sourceDpiX = qt_defaultDpi();
1730 qreal sourceDpiY = sourceDpiX;
1731
1732 QPaintDevice *dev = doc->documentLayout()->paintDevice();
1733 if (dev) {
1734 sourceDpiX = dev->logicalDpiX();
1735 sourceDpiY = dev->logicalDpiY();
1736 }
1737
1738 const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
1739 const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
1740
1741 // scale to dpi
1742 p.scale(dpiScaleX, dpiScaleY);
1743
1744 QSizeF scaledPageSize = d->pageSize;
1745 scaledPageSize.rwidth() *= dpiScaleX;
1746 scaledPageSize.rheight() *= dpiScaleY;
1747
1748 const QSizeF printerPageSize(printer->pageRect().size());
1749
1750 // scale to page
1751 p.scale(printerPageSize.width() / scaledPageSize.width(),
1752 printerPageSize.height() / scaledPageSize.height());
1753 } else {
1754 doc = clone(const_cast<QTextDocument *>(this));
1755 clonedDoc.reset(const_cast<QTextDocument *>(doc));
1756
1757 for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock();
1758 srcBlock.isValid() && dstBlock.isValid();
1759 srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
1760 dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats());
1761 }
1762
1763 QAbstractTextDocumentLayout *layout = doc->documentLayout();
1764 layout->setPaintDevice(p.device());
1765
1766 // copy the custom object handlers
1767 layout->d_func()->handlers = documentLayout()->d_func()->handlers;
1768
1769 int dpiy = p.device()->logicalDpiY();
1770 int margin = 0;
1771 if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) {
1772 // for compatibility
1773 margin = (int) ((2/2.54)*dpiy); // 2 cm margins
1774 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
1775 fmt.setMargin(margin);
1776 doc->rootFrame()->setFrameFormat(fmt);
1777 }
1778
1779 QRectF pageRect(printer->pageRect());
1780 body = QRectF(0, 0, pageRect.width(), pageRect.height());
1781 pageNumberPos = QPointF(body.width() - margin,
1782 body.height() - margin
1783 + QFontMetrics(doc->defaultFont(), p.device()).ascent()
1784 + 5 * dpiy / 72.0);
1785 clonedDoc->setPageSize(body.size());
1786 }
1787
1788 int docCopies;
1789 int pageCopies;
1790 if (printer->collateCopies() == true){
1791 docCopies = 1;
1792 pageCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount();
1793 } else {
1794 docCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount();
1795 pageCopies = 1;
1796 }
1797
1798 int fromPage = printer->fromPage();
1799 int toPage = printer->toPage();
1800 bool ascending = true;
1801
1802 if (fromPage == 0 && toPage == 0) {
1803 fromPage = 1;
1804 toPage = doc->pageCount();
1805 }
1806 // paranoia check
1807 fromPage = qMax(1, fromPage);
1808 toPage = qMin(doc->pageCount(), toPage);
1809
1810 if (toPage < fromPage) {
1811 // if the user entered a page range outside the actual number
1812 // of printable pages, just return
1813 return;
1814 }
1815
1816 if (printer->pageOrder() == QPrinter::LastPageFirst) {
1817 int tmp = fromPage;
1818 fromPage = toPage;
1819 toPage = tmp;
1820 ascending = false;
1821 }
1822
1823 for (int i = 0; i < docCopies; ++i) {
1824
1825 int page = fromPage;
1826 while (true) {
1827 for (int j = 0; j < pageCopies; ++j) {
1828 if (printer->printerState() == QPrinter::Aborted
1829 || printer->printerState() == QPrinter::Error)
1830 return;
1831 printPage(page, &p, doc, body, pageNumberPos);
1832 if (j < pageCopies - 1)
1833 printer->newPage();
1834 }
1835
1836 if (page == toPage)
1837 break;
1838
1839 if (ascending)
1840 ++page;
1841 else
1842 --page;
1843
1844 printer->newPage();
1845 }
1846
1847 if ( i < docCopies - 1)
1848 printer->newPage();
1849 }
1850}
1851#endif
1852
1853/*!
1854 \enum QTextDocument::ResourceType
1855
1856 This enum describes the types of resources that can be loaded by
1857 QTextDocument's loadResource() function.
1858
1859 \value HtmlResource The resource contains HTML.
1860 \value ImageResource The resource contains image data.
1861 Currently supported data types are QVariant::Pixmap and
1862 QVariant::Image. If the corresponding variant is of type
1863 QVariant::ByteArray then Qt attempts to load the image using
1864 QImage::loadFromData. QVariant::Icon is currently not supported.
1865 The icon needs to be converted to one of the supported types first,
1866 for example using QIcon::pixmap.
1867 \value StyleSheetResource The resource contains CSS.
1868 \value UserResource The first available value for user defined
1869 resource types.
1870
1871 \sa loadResource()
1872*/
1873
1874/*!
1875 Returns data of the specified \a type from the resource with the
1876 given \a name.
1877
1878 This function is called by the rich text engine to request data that isn't
1879 directly stored by QTextDocument, but still associated with it. For example,
1880 images are referenced indirectly by the name attribute of a QTextImageFormat
1881 object.
1882
1883 Resources are cached internally in the document. If a resource can
1884 not be found in the cache, loadResource is called to try to load
1885 the resource. loadResource should then use addResource to add the
1886 resource to the cache.
1887
1888 \sa QTextDocument::ResourceType
1889*/
1890QVariant QTextDocument::resource(int type, const QUrl &name) const
1891{
1892 Q_D(const QTextDocument);
1893 QVariant r = d->resources.value(name);
1894 if (!r.isValid()) {
1895 r = d->cachedResources.value(name);
1896 if (!r.isValid())
1897 r = const_cast<QTextDocument *>(this)->loadResource(type, name);
1898 }
1899 return r;
1900}
1901
1902/*!
1903 Adds the resource \a resource to the resource cache, using \a
1904 type and \a name as identifiers. \a type should be a value from
1905 QTextDocument::ResourceType.
1906
1907 For example, you can add an image as a resource in order to reference it
1908 from within the document:
1909
1910 \snippet snippets/textdocument-resources/main.cpp Adding a resource
1911
1912 The image can be inserted into the document using the QTextCursor API:
1913
1914 \snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor
1915
1916 Alternatively, you can insert images using the HTML \c img tag:
1917
1918 \snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML
1919*/
1920void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
1921{
1922 Q_UNUSED(type);
1923 Q_D(QTextDocument);
1924 d->resources.insert(name, resource);
1925}
1926
1927/*!
1928 Loads data of the specified \a type from the resource with the
1929 given \a name.
1930
1931 This function is called by the rich text engine to request data that isn't
1932 directly stored by QTextDocument, but still associated with it. For example,
1933 images are referenced indirectly by the name attribute of a QTextImageFormat
1934 object.
1935
1936 When called by Qt, \a type is one of the values of
1937 QTextDocument::ResourceType.
1938
1939 If the QTextDocument is a child object of a QTextEdit, QTextBrowser,
1940 or a QTextDocument itself then the default implementation tries
1941 to retrieve the data from the parent.
1942*/
1943QVariant QTextDocument::loadResource(int type, const QUrl &name)
1944{
1945 Q_D(QTextDocument);
1946 QVariant r;
1947
1948 QTextDocument *doc = qobject_cast<QTextDocument *>(parent());
1949 if (doc) {
1950 r = doc->loadResource(type, name);
1951 }
1952#ifndef QT_NO_TEXTEDIT
1953 else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) {
1954 QUrl resolvedName = edit->d_func()->resolveUrl(name);
1955 r = edit->loadResource(type, resolvedName);
1956 }
1957#endif
1958#ifndef QT_NO_TEXTCONTROL
1959 else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) {
1960 r = control->loadResource(type, name);
1961 }
1962#endif
1963
1964 // handle data: URLs
1965 if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0)
1966 r = qDecodeDataUrl(name).second;
1967
1968 // if resource was not loaded try to load it here
1969 if (!doc && r.isNull() && name.isRelative()) {
1970 QUrl currentURL = d->url;
1971 QUrl resourceUrl = name;
1972
1973 // For the second case QUrl can merge "#someanchor" with "foo.html"
1974 // correctly to "foo.html#someanchor"
1975 if (!(currentURL.isRelative()
1976 || (currentURL.scheme() == QLatin1String("file")
1977 && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
1978 || (name.hasFragment() && name.path().isEmpty())) {
1979 resourceUrl = currentURL.resolved(name);
1980 } else {
1981 // this is our last resort when current url and new url are both relative
1982 // we try to resolve against the current working directory in the local
1983 // file system.
1984 QFileInfo fi(currentURL.toLocalFile());
1985 if (fi.exists()) {
1986 resourceUrl =
1987 QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name);
1988 }
1989 }
1990
1991 QString s = resourceUrl.toLocalFile();
1992 QFile f(s);
1993 if (!s.isEmpty() && f.open(QFile::ReadOnly)) {
1994 r = f.readAll();
1995 f.close();
1996 }
1997 }
1998
1999 if (!r.isNull()) {
2000 if (type == ImageResource && r.type() == QVariant::ByteArray) {
2001 if (qApp->thread() != QThread::currentThread()) {
2002 // must use images in non-GUI threads
2003 QImage image;
2004 image.loadFromData(r.toByteArray());
2005 if (!image.isNull())
2006 r = image;
2007 } else {
2008 QPixmap pm;
2009 pm.loadFromData(r.toByteArray());
2010 if (!pm.isNull())
2011 r = pm;
2012 }
2013 }
2014 d->cachedResources.insert(name, r);
2015 }
2016 return r;
2017}
2018
2019static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
2020{
2021 QTextFormat diff = to;
2022
2023 const QMap<int, QVariant> props = to.properties();
2024 for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
2025 it != end; ++it)
2026 if (it.value() == from.property(it.key()))
2027 diff.clearProperty(it.key());
2028
2029 return diff;
2030}
2031
2032QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
2033 : doc(_doc), fragmentMarkers(false)
2034{
2035 const QFont defaultFont = doc->defaultFont();
2036 defaultCharFormat.setFont(defaultFont);
2037 // don't export those for the default font since we cannot turn them off with CSS
2038 defaultCharFormat.clearProperty(QTextFormat::FontUnderline);
2039 defaultCharFormat.clearProperty(QTextFormat::FontOverline);
2040 defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut);
2041 defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle);
2042}
2043
2044/*!
2045 Returns the document in HTML format. The conversion may not be
2046 perfect, especially for complex documents, due to the limitations
2047 of HTML.
2048*/
2049QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode)
2050{
2051 html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2052 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2053 "<html><head><meta name=\"qrichtext\" content=\"1\" />");
2054 html.reserve(doc->docHandle()->length());
2055
2056 fragmentMarkers = (mode == ExportFragment);
2057
2058 if (!encoding.isEmpty())
2059 html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding));
2060
2061 QString title = doc->metaInformation(QTextDocument::DocumentTitle);
2062 if (!title.isEmpty())
2063 html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
2064 html += QLatin1String("<style type=\"text/css\">\n");
2065 html += QLatin1String("p, li { white-space: pre-wrap; }\n");
2066 html += QLatin1String("</style>");
2067 html += QLatin1String("</head><body");
2068
2069 if (mode == ExportEntireDocument) {
2070 html += QLatin1String(" style=\"");
2071
2072 emitFontFamily(defaultCharFormat.fontFamily());
2073
2074 if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
2075 html += QLatin1String(" font-size:");
2076 html += QString::number(defaultCharFormat.fontPointSize());
2077 html += QLatin1String("pt;");
2078 }
2079
2080 html += QLatin1String(" font-weight:");
2081 html += QString::number(defaultCharFormat.fontWeight() * 8);
2082 html += QLatin1Char(';');
2083
2084 html += QLatin1String(" font-style:");
2085 html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2086 html += QLatin1Char(';');
2087
2088 // do not set text-decoration on the default font since those values are /always/ propagated
2089 // and cannot be turned off with CSS
2090
2091 html += QLatin1Char('\"');
2092
2093 const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2094 emitBackgroundAttribute(fmt);
2095
2096 } else {
2097 defaultCharFormat = QTextCharFormat();
2098 }
2099 html += QLatin1Char('>');
2100
2101 QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
2102 rootFmt.clearProperty(QTextFormat::BackgroundBrush);
2103
2104 QTextFrameFormat defaultFmt;
2105 defaultFmt.setMargin(doc->documentMargin());
2106
2107 if (rootFmt == defaultFmt)
2108 emitFrame(doc->rootFrame()->begin());
2109 else
2110 emitTextFrame(doc->rootFrame());
2111
2112 html += QLatin1String("</body></html>");
2113 return html;
2114}
2115
2116void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
2117{
2118 html += QLatin1Char(' ');
2119 html += QLatin1String(attribute);
2120 html += QLatin1String("=\"");
2121 html += Qt::escape(value);
2122 html += QLatin1Char('"');
2123}
2124
2125bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
2126{
2127 bool attributesEmitted = false;
2128
2129 {
2130 const QString family = format.fontFamily();
2131 if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
2132 emitFontFamily(family);
2133 attributesEmitted = true;
2134 }
2135 }
2136
2137 if (format.hasProperty(QTextFormat::FontPointSize)
2138 && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
2139 html += QLatin1String(" font-size:");
2140 html += QString::number(format.fontPointSize());
2141 html += QLatin1String("pt;");
2142 attributesEmitted = true;
2143 } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
2144 static const char * const sizeNames[] = {
2145 "small", "medium", "large", "x-large", "xx-large"
2146 };
2147 const char *name = 0;
2148 const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
2149 if (idx >= 0 && idx <= 4) {
2150 name = sizeNames[idx];
2151 }
2152 if (name) {
2153 html += QLatin1String(" font-size:");
2154 html += QLatin1String(name);
2155 html += QLatin1Char(';');
2156 attributesEmitted = true;
2157 }
2158 }
2159
2160 if (format.hasProperty(QTextFormat::FontWeight)
2161 && format.fontWeight() != defaultCharFormat.fontWeight()) {
2162 html += QLatin1String(" font-weight:");
2163 html += QString::number(format.fontWeight() * 8);
2164 html += QLatin1Char(';');
2165 attributesEmitted = true;
2166 }
2167
2168 if (format.hasProperty(QTextFormat::FontItalic)
2169 && format.fontItalic() != defaultCharFormat.fontItalic()) {
2170 html += QLatin1String(" font-style:");
2171 html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2172 html += QLatin1Char(';');
2173 attributesEmitted = true;
2174 }
2175
2176 QLatin1String decorationTag(" text-decoration:");
2177 html += decorationTag;
2178 bool hasDecoration = false;
2179 bool atLeastOneDecorationSet = false;
2180
2181 if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
2182 && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
2183 hasDecoration = true;
2184 if (format.fontUnderline()) {
2185 html += QLatin1String(" underline");
2186 atLeastOneDecorationSet = true;
2187 }
2188 }
2189
2190 if (format.hasProperty(QTextFormat::FontOverline)
2191 && format.fontOverline() != defaultCharFormat.fontOverline()) {
2192 hasDecoration = true;
2193 if (format.fontOverline()) {
2194 html += QLatin1String(" overline");
2195 atLeastOneDecorationSet = true;
2196 }
2197 }
2198
2199 if (format.hasProperty(QTextFormat::FontStrikeOut)
2200 && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
2201 hasDecoration = true;
2202 if (format.fontStrikeOut()) {
2203 html += QLatin1String(" line-through");
2204 atLeastOneDecorationSet = true;
2205 }
2206 }
2207
2208 if (hasDecoration) {
2209 if (!atLeastOneDecorationSet)
2210 html += QLatin1String("none");
2211 html += QLatin1Char(';');
2212 attributesEmitted = true;
2213 } else {
2214 html.chop(qstrlen(decorationTag.latin1()));
2215 }
2216
2217 if (format.foreground() != defaultCharFormat.foreground()
2218 && format.foreground().style() != Qt::NoBrush) {
2219 html += QLatin1String(" color:");
2220 html += format.foreground().color().name();
2221 html += QLatin1Char(';');
2222 attributesEmitted = true;
2223 }
2224
2225 if (format.background() != defaultCharFormat.background()
2226 && format.background().style() == Qt::SolidPattern) {
2227 html += QLatin1String(" background-color:");
2228 html += format.background().color().name();
2229 html += QLatin1Char(';');
2230 attributesEmitted = true;
2231 }
2232
2233 if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
2234 && format.verticalAlignment() != QTextCharFormat::AlignNormal)
2235 {
2236 html += QLatin1String(" vertical-align:");
2237
2238 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2239 if (valign == QTextCharFormat::AlignSubScript)
2240 html += QLatin1String("sub");
2241 else if (valign == QTextCharFormat::AlignSuperScript)
2242 html += QLatin1String("super");
2243 else if (valign == QTextCharFormat::AlignMiddle)
2244 html += QLatin1String("middle");
2245 else if (valign == QTextCharFormat::AlignTop)
2246 html += QLatin1String("top");
2247 else if (valign == QTextCharFormat::AlignBottom)
2248 html += QLatin1String("bottom");
2249
2250 html += QLatin1Char(';');
2251 attributesEmitted = true;
2252 }
2253
2254 if (format.fontCapitalization() != QFont::MixedCase) {
2255 const QFont::Capitalization caps = format.fontCapitalization();
2256 if (caps == QFont::AllUppercase)
2257 html += QLatin1String(" text-transform:uppercase;");
2258 else if (caps == QFont::AllLowercase)
2259 html += QLatin1String(" text-transform:lowercase;");
2260 else if (caps == QFont::SmallCaps)
2261 html += QLatin1String(" font-variant:small-caps;");
2262 attributesEmitted = true;
2263 }
2264
2265 if (format.fontWordSpacing() != 0.0) {
2266 html += QLatin1String(" word-spacing:");
2267 html += QString::number(format.fontWordSpacing());
2268 html += QLatin1String("px;");
2269 attributesEmitted = true;
2270 }
2271
2272 return attributesEmitted;
2273}
2274
2275void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
2276{
2277 if (length.type() == QTextLength::VariableLength) // default
2278 return;
2279
2280 html += QLatin1Char(' ');
2281 html += QLatin1String(attribute);
2282 html += QLatin1String("=\"");
2283 html += QString::number(length.rawValue());
2284
2285 if (length.type() == QTextLength::PercentageLength)
2286 html += QLatin1String("%\"");
2287 else
2288 html += QLatin1Char('\"');
2289}
2290
2291void QTextHtmlExporter::emitAlignment(Qt::Alignment align)
2292{
2293 if (align & Qt::AlignLeft)
2294 return;
2295 else if (align & Qt::AlignRight)
2296 html += QLatin1String(" align=\"right\"");
2297 else if (align & Qt::AlignHCenter)
2298 html += QLatin1String(" align=\"center\"");
2299 else if (align & Qt::AlignJustify)
2300 html += QLatin1String(" align=\"justify\"");
2301}
2302
2303void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode)
2304{
2305 if (pos == QTextFrameFormat::InFlow)
2306 return;
2307
2308 if (mode == EmitStyleTag)
2309 html += QLatin1String(" style=\"float:");
2310 else
2311 html += QLatin1String(" float:");
2312
2313 if (pos == QTextFrameFormat::FloatLeft)
2314 html += QLatin1String(" left;");
2315 else if (pos == QTextFrameFormat::FloatRight)
2316 html += QLatin1String(" right;");
2317 else
2318 Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
2319
2320 if (mode == EmitStyleTag)
2321 html += QLatin1Char('\"');
2322}
2323
2324void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
2325{
2326 Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
2327
2328 html += QLatin1String(" border-style:");
2329
2330 switch (style) {
2331 case QTextFrameFormat::BorderStyle_None:
2332 html += QLatin1String("none");
2333 break;
2334 case QTextFrameFormat::BorderStyle_Dotted:
2335 html += QLatin1String("dotted");
2336 break;
2337 case QTextFrameFormat::BorderStyle_Dashed:
2338 html += QLatin1String("dashed");
2339 break;
2340 case QTextFrameFormat::BorderStyle_Solid:
2341 html += QLatin1String("solid");
2342 break;
2343 case QTextFrameFormat::BorderStyle_Double:
2344 html += QLatin1String("double");
2345 break;
2346 case QTextFrameFormat::BorderStyle_DotDash:
2347 html += QLatin1String("dot-dash");
2348 break;
2349 case QTextFrameFormat::BorderStyle_DotDotDash:
2350 html += QLatin1String("dot-dot-dash");
2351 break;
2352 case QTextFrameFormat::BorderStyle_Groove:
2353 html += QLatin1String("groove");
2354 break;
2355 case QTextFrameFormat::BorderStyle_Ridge:
2356 html += QLatin1String("ridge");
2357 break;
2358 case QTextFrameFormat::BorderStyle_Inset:
2359 html += QLatin1String("inset");
2360 break;
2361 case QTextFrameFormat::BorderStyle_Outset:
2362 html += QLatin1String("outset");
2363 break;
2364 default:
2365 Q_ASSERT(false);
2366 break;
2367 };
2368
2369 html += QLatin1Char(';');
2370}
2371
2372void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)
2373{
2374 if (policy & QTextFormat::PageBreak_AlwaysBefore)
2375 html += QLatin1String(" page-break-before:always;");
2376
2377 if (policy & QTextFormat::PageBreak_AlwaysAfter)
2378 html += QLatin1String(" page-break-after:always;");
2379}
2380
2381void QTextHtmlExporter::emitFontFamily(const QString &family)
2382{
2383 html += QLatin1String(" font-family:");
2384
2385 QLatin1String quote("\'");
2386 if (family.contains(QLatin1Char('\'')))
2387 quote = QLatin1String("&quot;");
2388
2389 html += quote;
2390 html += Qt::escape(family);
2391 html += quote;
2392 html += QLatin1Char(';');
2393}
2394
2395void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
2396{
2397 html += QLatin1String(" margin-top:");
2398 html += top;
2399 html += QLatin1String("px;");
2400
2401 html += QLatin1String(" margin-bottom:");
2402 html += bottom;
2403 html += QLatin1String("px;");
2404
2405 html += QLatin1String(" margin-left:");
2406 html += left;
2407 html += QLatin1String("px;");
2408
2409 html += QLatin1String(" margin-right:");
2410 html += right;
2411 html += QLatin1String("px;");
2412}
2413
2414void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
2415{
2416 const QTextCharFormat format = fragment.charFormat();
2417
2418 bool closeAnchor = false;
2419
2420 if (format.isAnchor()) {
2421 const QString name = format.anchorName();
2422 if (!name.isEmpty()) {
2423 html += QLatin1String("<a name=\"");
2424 html += Qt::escape(name);
2425 html += QLatin1String("\"></a>");
2426 }
2427 const QString href = format.anchorHref();
2428 if (!href.isEmpty()) {
2429 html += QLatin1String("<a href=\"");
2430 html += Qt::escape(href);
2431 html += QLatin1String("\">");
2432 closeAnchor = true;
2433 }
2434 }
2435
2436 QString txt = fragment.text();
2437 const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
2438 const bool isImage = isObject && format.isImageFormat();
2439
2440 QLatin1String styleTag("<span style=\"");
2441 html += styleTag;
2442
2443 bool attributesEmitted = false;
2444 if (!isImage)
2445 attributesEmitted = emitCharFormatStyle(format);
2446 if (attributesEmitted)
2447 html += QLatin1String("\">");
2448 else
2449 html.chop(qstrlen(styleTag.latin1()));
2450
2451 if (isObject) {
2452 for (int i = 0; isImage && i < txt.length(); ++i) {
2453 QTextImageFormat imgFmt = format.toImageFormat();
2454
2455 html += QLatin1String("<img");
2456
2457 if (imgFmt.hasProperty(QTextFormat::ImageName))
2458 emitAttribute("src", imgFmt.name());
2459
2460 if (imgFmt.hasProperty(QTextFormat::ImageWidth))
2461 emitAttribute("width", QString::number(imgFmt.width()));
2462
2463 if (imgFmt.hasProperty(QTextFormat::ImageHeight))
2464 emitAttribute("height", QString::number(imgFmt.height()));
2465
2466 if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
2467 html += QLatin1String(" style=\"vertical-align: middle;\"");
2468 else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
2469 html += QLatin1String(" style=\"vertical-align: top;\"");
2470
2471 if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
2472 emitFloatStyle(imageFrame->frameFormat().position());
2473
2474 html += QLatin1String(" />");
2475 }
2476 } else {
2477 Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
2478
2479 txt = Qt::escape(txt);
2480
2481 // split for [\n{LineSeparator}]
2482 QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
2483 forcedLineBreakRegExp[3] = QChar::LineSeparator;
2484
2485 const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp));
2486 for (int i = 0; i < lines.count(); ++i) {
2487 if (i > 0)
2488 html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
2489 html += lines.at(i);
2490 }
2491 }
2492
2493 if (attributesEmitted)
2494 html += QLatin1String("</span>");
2495
2496 if (closeAnchor)
2497 html += QLatin1String("</a>");
2498}
2499
2500static bool isOrderedList(int style)
2501{
2502 return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
2503 || style == QTextListFormat::ListUpperAlpha
2504 || style == QTextListFormat::ListUpperRoman
2505 || style == QTextListFormat::ListLowerRoman
2506 ;
2507}
2508
2509void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
2510{
2511 QTextBlockFormat format = block.blockFormat();
2512 emitAlignment(format.alignment());
2513
2514 // assume default to not bloat the html too much
2515 // html += QLatin1String(" dir='ltr'");
2516 if (block.textDirection() == Qt::RightToLeft)
2517 html += QLatin1String(" dir='rtl'");
2518
2519 QLatin1String style(" style=\"");
2520 html += style;
2521
2522 const bool emptyBlock = block.begin().atEnd();
2523 if (emptyBlock) {
2524 html += QLatin1String("-qt-paragraph-type:empty;");
2525 }
2526
2527 emitMargins(QString::number(format.topMargin()),
2528 QString::number(format.bottomMargin()),
2529 QString::number(format.leftMargin()),
2530 QString::number(format.rightMargin()));
2531
2532 html += QLatin1String(" -qt-block-indent:");
2533 html += QString::number(format.indent());
2534 html += QLatin1Char(';');
2535
2536 html += QLatin1String(" text-indent:");
2537 html += QString::number(format.textIndent());
2538 html += QLatin1String("px;");
2539
2540 if (block.userState() != -1) {
2541 html += QLatin1String(" -qt-user-state:");
2542 html += QString::number(block.userState());
2543 html += QLatin1Char(';');
2544 }
2545
2546 emitPageBreakPolicy(format.pageBreakPolicy());
2547
2548 QTextCharFormat diff;
2549 if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag
2550 const QTextCharFormat blockCharFmt = block.charFormat();
2551 diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat();
2552 }
2553
2554 diff.clearProperty(QTextFormat::BackgroundBrush);
2555 if (format.hasProperty(QTextFormat::BackgroundBrush)) {
2556 QBrush bg = format.background();
2557 if (bg.style() != Qt::NoBrush)
2558 diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush));
2559 }
2560
2561 if (!diff.properties().isEmpty())
2562 emitCharFormatStyle(diff);
2563
2564 html += QLatin1Char('"');
2565
2566}
2567
2568void QTextHtmlExporter::emitBlock(const QTextBlock &block)
2569{
2570 if (block.begin().atEnd()) {
2571 // ### HACK, remove once QTextFrame::Iterator is fixed
2572 int p = block.position();
2573 if (p > 0)
2574 --p;
2575 QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
2576 QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
2577 if (ch == QTextBeginningOfFrame
2578 || ch == QTextEndOfFrame)
2579 return;
2580 }
2581
2582 html += QLatin1Char('\n');
2583
2584 // save and later restore, in case we 'change' the default format by
2585 // emitting block char format information
2586 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
2587
2588 QTextList *list = block.textList();
2589 if (list) {
2590 if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
2591 const QTextListFormat format = list->format();
2592 const int style = format.style();
2593 switch (style) {
2594 case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
2595 case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
2596 case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break;
2597 case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break;
2598 case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break;
2599 case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break;
2600 case QTextListFormat::ListLowerRoman: html += QLatin1String("<ol type=\"i\""); break;
2601 case QTextListFormat::ListUpperRoman: html += QLatin1String("<ol type=\"I\""); break;
2602 default: html += QLatin1String("<ul"); // ### should not happen
2603 }
2604
2605 html += QLatin1String(" style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
2606
2607 if (format.hasProperty(QTextFormat::ListIndent)) {
2608 html += QLatin1String(" -qt-list-indent: ");
2609 html += QString::number(format.indent());
2610 html += QLatin1Char(';');
2611 }
2612
2613 html += QLatin1String("\">");
2614 }
2615
2616 html += QLatin1String("<li");
2617
2618 const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
2619 if (!blockFmt.properties().isEmpty()) {
2620 html += QLatin1String(" style=\"");
2621 emitCharFormatStyle(blockFmt);
2622 html += QLatin1Char('\"');
2623
2624 defaultCharFormat.merge(block.charFormat());
2625 }
2626 }
2627
2628 const QTextBlockFormat blockFormat = block.blockFormat();
2629 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
2630 html += QLatin1String("<hr");
2631
2632 QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
2633 if (width.type() != QTextLength::VariableLength)
2634 emitTextLength("width", width);
2635 else
2636 html += QLatin1Char(' ');
2637
2638 html += QLatin1String("/>");
2639 return;
2640 }
2641
2642 const bool pre = blockFormat.nonBreakableLines();
2643 if (pre) {
2644 if (list)
2645 html += QLatin1Char('>');
2646 html += QLatin1String("<pre");
2647 } else if (!list) {
2648 html += QLatin1String("<p");
2649 }
2650
2651 emitBlockAttributes(block);
2652
2653 html += QLatin1Char('>');
2654
2655 QTextBlock::Iterator it = block.begin();
2656 if (fragmentMarkers && !it.atEnd() && block == doc->begin())
2657 html += QLatin1String("<!--StartFragment-->");
2658
2659 for (; !it.atEnd(); ++it)
2660 emitFragment(it.fragment());
2661
2662 if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
2663 html += QLatin1String("<!--EndFragment-->");
2664
2665 if (pre)
2666 html += QLatin1String("</pre>");
2667 else if (list)
2668 html += QLatin1String("</li>");
2669 else
2670 html += QLatin1String("</p>");
2671
2672 if (list) {
2673 if (list->itemNumber(block) == list->count() - 1) { // last item? close list
2674 if (isOrderedList(list->format().style()))
2675 html += QLatin1String("</ol>");
2676 else
2677 html += QLatin1String("</ul>");
2678 }
2679 }
2680
2681 defaultCharFormat = oldDefaultCharFormat;
2682}
2683
2684extern bool qHasPixmapTexture(const QBrush& brush);
2685
2686QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
2687{
2688 QString url;
2689 if (!doc)
2690 return url;
2691
2692 if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
2693 return findUrlForImage(parent, cacheKey, isPixmap);
2694
2695 if (doc && doc->docHandle()) {
2696 QTextDocumentPrivate *priv = doc->docHandle();
2697 QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
2698 for (; it != priv->cachedResources.constEnd(); ++it) {
2699
2700 const QVariant &v = it.value();
2701 if (v.type() == QVariant::Image && !isPixmap) {
2702 if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
2703 break;
2704 }
2705
2706 if (v.type() == QVariant::Pixmap && isPixmap) {
2707 if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
2708 break;
2709 }
2710 }
2711
2712 if (it != priv->cachedResources.constEnd())
2713 url = it.key().toString();
2714 }
2715
2716 return url;
2717}
2718
2719void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
2720{
2721 if (!priv)
2722 return;
2723
2724 cachedResources.unite(priv->cachedResources);
2725}
2726
2727void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
2728{
2729 if (format.hasProperty(QTextFormat::BackgroundImageUrl)) {
2730 QString url = format.property(QTextFormat::BackgroundImageUrl).toString();
2731 emitAttribute("background", url);
2732 } else {
2733 const QBrush &brush = format.background();
2734 if (brush.style() == Qt::SolidPattern) {
2735 emitAttribute("bgcolor", brush.color().name());
2736 } else if (brush.style() == Qt::TexturePattern) {
2737 const bool isPixmap = qHasPixmapTexture(brush);
2738 const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
2739
2740 const QString url = findUrlForImage(doc, cacheKey, isPixmap);
2741
2742 if (!url.isEmpty())
2743 emitAttribute("background", url);
2744 }
2745 }
2746}
2747
2748void QTextHtmlExporter::emitTable(const QTextTable *table)
2749{
2750 QTextTableFormat format = table->format();
2751
2752 html += QLatin1String("\n<table");
2753
2754 if (format.hasProperty(QTextFormat::FrameBorder))
2755 emitAttribute("border", QString::number(format.border()));
2756
2757 emitFrameStyle(format, TableFrame);
2758
2759 emitAlignment(format.alignment());
2760 emitTextLength("width", format.width());
2761
2762 if (format.hasProperty(QTextFormat::TableCellSpacing))
2763 emitAttribute("cellspacing", QString::number(format.cellSpacing()));
2764 if (format.hasProperty(QTextFormat::TableCellPadding))
2765 emitAttribute("cellpadding", QString::number(format.cellPadding()));
2766
2767 emitBackgroundAttribute(format);
2768
2769 html += QLatin1Char('>');
2770
2771 const int rows = table->rows();
2772 const int columns = table->columns();
2773
2774 QVector<QTextLength> columnWidths = format.columnWidthConstraints();
2775 if (columnWidths.isEmpty()) {
2776 columnWidths.resize(columns);
2777 columnWidths.fill(QTextLength());
2778 }
2779 Q_ASSERT(columnWidths.count() == columns);
2780
2781 QVarLengthArray<bool> widthEmittedForColumn(columns);
2782 for (int i = 0; i < columns; ++i)
2783 widthEmittedForColumn[i] = false;
2784
2785 const int headerRowCount = qMin(format.headerRowCount(), rows);
2786 if (headerRowCount > 0)
2787 html += QLatin1String("<thead>");
2788
2789 for (int row = 0; row < rows; ++row) {
2790 html += QLatin1String("\n<tr>");
2791
2792 for (int col = 0; col < columns; ++col) {
2793 const QTextTableCell cell = table->cellAt(row, col);
2794
2795 // for col/rowspans
2796 if (cell.row() != row)
2797 continue;
2798
2799 if (cell.column() != col)
2800 continue;
2801
2802 html += QLatin1String("\n<td");
2803
2804 if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
2805 emitTextLength("width", columnWidths.at(col));
2806 widthEmittedForColumn[col] = true;
2807 }
2808
2809 if (cell.columnSpan() > 1)
2810 emitAttribute("colspan", QString::number(cell.columnSpan()));
2811
2812 if (cell.rowSpan() > 1)
2813 emitAttribute("rowspan", QString::number(cell.rowSpan()));
2814
2815 const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
2816 emitBackgroundAttribute(cellFormat);
2817
2818 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
2819
2820 QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment();
2821
2822 QString styleString;
2823 if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) {
2824 styleString += QLatin1String(" vertical-align:");
2825 switch (valign) {
2826 case QTextCharFormat::AlignMiddle:
2827 styleString += QLatin1String("middle");
2828 break;
2829 case QTextCharFormat::AlignTop:
2830 styleString += QLatin1String("top");
2831 break;
2832 case QTextCharFormat::AlignBottom:
2833 styleString += QLatin1String("bottom");
2834 break;
2835 default:
2836 break;
2837 }
2838 styleString += QLatin1Char(';');
2839
2840 QTextCharFormat temp;
2841 temp.setVerticalAlignment(valign);
2842 defaultCharFormat.merge(temp);
2843 }
2844
2845 if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding))
2846 styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';');
2847 if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding))
2848 styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';');
2849 if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding))
2850 styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';');
2851 if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding))
2852 styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';');
2853
2854 if (!styleString.isEmpty())
2855 html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"');
2856
2857 html += QLatin1Char('>');
2858
2859 emitFrame(cell.begin());
2860
2861 html += QLatin1String("</td>");
2862
2863 defaultCharFormat = oldDefaultCharFormat;
2864 }
2865
2866 html += QLatin1String("</tr>");
2867 if (headerRowCount > 0 && row == headerRowCount - 1)
2868 html += QLatin1String("</thead>");
2869 }
2870
2871 html += QLatin1String("</table>");
2872}
2873
2874void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt)
2875{
2876 if (!frameIt.atEnd()) {
2877 QTextFrame::Iterator next = frameIt;
2878 ++next;
2879 if (next.atEnd()
2880 && frameIt.currentFrame() == 0
2881 && frameIt.parentFrame() != doc->rootFrame()
2882 && frameIt.currentBlock().begin().atEnd())
2883 return;
2884 }
2885
2886 for (QTextFrame::Iterator it = frameIt;
2887 !it.atEnd(); ++it) {
2888 if (QTextFrame *f = it.currentFrame()) {
2889 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2890 emitTable(table);
2891 } else {
2892 emitTextFrame(f);
2893 }
2894 } else if (it.currentBlock().isValid()) {
2895 emitBlock(it.currentBlock());
2896 }
2897 }
2898}
2899
2900void QTextHtmlExporter::emitTextFrame(const QTextFrame *f)
2901{
2902 FrameType frameType = f->parentFrame() ? TextFrame : RootFrame;
2903
2904 html += QLatin1String("\n<table");
2905 QTextFrameFormat format = f->frameFormat();
2906
2907 if (format.hasProperty(QTextFormat::FrameBorder))
2908 emitAttribute("border", QString::number(format.border()));
2909
2910 emitFrameStyle(format, frameType);
2911
2912 emitTextLength("width", format.width());
2913 emitTextLength("height", format.height());
2914
2915 // root frame's bcolor goes in the <body> tag
2916 if (frameType != RootFrame)
2917 emitBackgroundAttribute(format);
2918
2919 html += QLatin1Char('>');
2920 html += QLatin1String("\n<tr>\n<td style=\"border: none;\">");
2921 emitFrame(f->begin());
2922 html += QLatin1String("</td></tr></table>");
2923}
2924
2925void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType)
2926{
2927 QLatin1String styleAttribute(" style=\"");
2928 html += styleAttribute;
2929 const int originalHtmlLength = html.length();
2930
2931 if (frameType == TextFrame)
2932 html += QLatin1String("-qt-table-type: frame;");
2933 else if (frameType == RootFrame)
2934 html += QLatin1String("-qt-table-type: root;");
2935
2936 const QTextFrameFormat defaultFormat;
2937
2938 emitFloatStyle(format.position(), OmitStyleTag);
2939 emitPageBreakPolicy(format.pageBreakPolicy());
2940
2941 if (format.borderBrush() != defaultFormat.borderBrush()) {
2942 html += QLatin1String(" border-color:");
2943 html += format.borderBrush().color().name();
2944 html += QLatin1Char(';');
2945 }
2946
2947 if (format.borderStyle() != defaultFormat.borderStyle())
2948 emitBorderStyle(format.borderStyle());
2949
2950 if (format.hasProperty(QTextFormat::FrameMargin)
2951 || format.hasProperty(QTextFormat::FrameLeftMargin)
2952 || format.hasProperty(QTextFormat::FrameRightMargin)
2953 || format.hasProperty(QTextFormat::FrameTopMargin)
2954 || format.hasProperty(QTextFormat::FrameBottomMargin))
2955 emitMargins(QString::number(format.topMargin()),
2956 QString::number(format.bottomMargin()),
2957 QString::number(format.leftMargin()),
2958 QString::number(format.rightMargin()));
2959
2960 if (html.length() == originalHtmlLength) // nothing emitted?
2961 html.chop(qstrlen(styleAttribute.latin1()));
2962 else
2963 html += QLatin1Char('\"');
2964}
2965
2966/*!
2967 Returns a string containing an HTML representation of the document.
2968
2969 The \a encoding parameter specifies the value for the charset attribute
2970 in the html header. For example if 'utf-8' is specified then the
2971 beginning of the generated html will look like this:
2972 \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1
2973
2974 If no encoding is specified then no such meta information is generated.
2975
2976 If you later on convert the returned html string into a byte array for
2977 transmission over a network or when saving to disk you should specify
2978 the encoding you're going to use for the conversion to a byte array here.
2979
2980 \sa {Supported HTML Subset}
2981*/
2982#ifndef QT_NO_TEXTHTMLPARSER
2983QString QTextDocument::toHtml(const QByteArray &encoding) const
2984{
2985 return QTextHtmlExporter(this).toHtml(encoding);
2986}
2987#endif // QT_NO_TEXTHTMLPARSER
2988
2989/*!
2990 Returns a vector of text formats for all the formats used in the document.
2991*/
2992QVector<QTextFormat> QTextDocument::allFormats() const
2993{
2994 Q_D(const QTextDocument);
2995 return d->formatCollection()->formats;
2996}
2997
2998
2999/*!
3000 \internal
3001
3002 So that not all classes have to be friends of each other...
3003*/
3004QTextDocumentPrivate *QTextDocument::docHandle() const
3005{
3006 Q_D(const QTextDocument);
3007 return const_cast<QTextDocumentPrivate *>(d);
3008}
3009
3010/*!
3011 \since 4.4
3012 \fn QTextDocument::undoCommandAdded()
3013
3014 This signal is emitted every time a new level of undo is added to the QTextDocument.
3015*/
3016
3017QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.