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