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