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