1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the text module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
#include "qtextdocument.h"
30
#include <qtextformat.h>
31
#include "qtextdocumentlayout_p.h"
32
#include "qtextdocumentfragment.h"
33
#include "qtextdocumentfragment_p.h"
34
#include "qtexttable.h"
35
#include "qtextlist.h"
38
#include <qvarlengtharray.h>
39
#include <qtextcodec.h>
40
#include "qtexthtmlparser_p.h"
43
#include "qtextedit.h"
45
#include "qtextdocument_p.h"
50
Returns true if the string \a text is likely to be rich text;
51
otherwise returns false.
53
This function uses a fast and therefore simple heuristic. It
54
mainly checks whether there is something that looks like a tag
55
before the first line break. Although the result may be correct
56
for common cases, there is no guarantee.
58
bool Qt::mightBeRichText(const QString& text)
64
while (start < text.length() && text.at(start).isSpace())
67
// skip a leading <?xml ... ?> as for example with xhtml
68
if (text.mid(start, 5) == QLatin1String("<?xml")) {
69
while (start < text.length()) {
70
if (text.at(start) == QLatin1Char('?')
71
&& start + 2 < text.length()
72
&& text.at(start + 1) == QLatin1Char('>')) {
79
while (start < text.length() && text.at(start).isSpace())
83
if (text.mid(start, 5).toLower() == QLatin1String("<!doc"))
86
while (open < text.length() && text.at(open) != '<'
87
&& text.at(open) != '\n') {
88
if (text.at(open) == '&' && text.mid(open+1,3) == "lt;")
89
return true; // support desperate attempt of user to see <...>
92
if (open < text.length() && text.at(open) == '<') {
93
const int close = text.indexOf('>', open);
96
for (int i = open+1; i < close; ++i) {
97
if (text[i].isDigit() || text[i].isLetter())
99
else if (!tag.isEmpty() && text[i].isSpace())
101
else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != '!'))
102
return false; // that's not a tag
104
return QTextHtmlParser::lookupElement(tag.toLower()) != -1;
111
Auxiliary function. Converts the plain text string \a plain to a
112
rich text formatted string with any HTML meta-characters escaped.
114
QString Qt::escape(const QString& plain)
117
rich.reserve(int(plain.length() * 1.1));
118
for (int i = 0; i < plain.length(); ++i) {
119
if (plain.at(i) == QLatin1Char('<'))
120
rich += QLatin1String("<");
121
else if (plain.at(i) == QLatin1Char('>'))
122
rich += QLatin1String(">");
123
else if (plain.at(i) == QLatin1Char('&'))
124
rich += QLatin1String("&");
132
\fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode)
134
Auxiliary function. Converts the plain text string \a plain to a
135
rich text formatted paragraph while preserving most of its look.
137
\a mode defines the whitespace mode. Possible values are \c
138
QStyleSheetItem::WhiteSpacePre (no wrapping, all whitespaces
139
preserved) and \c QStyleSheetItem::WhiteSpaceNormal (wrapping,
140
simplified whitespaces).
144
QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
149
for (int i = 0; i < plain.length(); ++i) {
150
if (plain[i] == '\n'){
152
while (i+1 < plain.length() && plain[i+1] == '\n') {
166
if (mode == Qt::WhiteSpacePre && plain[i] == '\t'){
174
else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
176
else if (plain[i] == '<')
178
else if (plain[i] == '>')
180
else if (plain[i] == '&')
195
QTextCodec *Qt::codecForHtml(const QByteArray &ba)
198
int mib = 4; // Latin1
202
if (ba.size() > 1 && (((uchar)ba[0] == 0xfe && (uchar)ba[1] == 0xff)
203
|| ((uchar)ba[0] == 0xff && (uchar)ba[1] == 0xfe))) {
205
} else if (ba.size() > 2
206
&& (uchar)ba[0] == 0xef
207
&& (uchar)ba[1] == 0xbb
208
&& (uchar)ba[2] == 0xbf) {
211
QByteArray header = ba.left(512).toLower();
212
if ((pos = header.indexOf("http-equiv=")) != -1) {
213
pos = header.indexOf("charset=", pos) + strlen("charset=");
215
int pos2 = header.indexOf('\"', pos+1);
216
QByteArray cs = header.mid(pos, pos2-pos);
217
// qDebug("found charset: %s", cs.data());
218
c = QTextCodec::codecForName(cs);
223
c = QTextCodec::codecForMib(mib);
228
// internal, do not Q_EXPORT
229
// can go away when QTextDocumentFragment uses QTextDocument
230
void qt_replace_special_text_characters(QString *text)
232
text->replace(QTextBeginningOfFrame, '\n');
233
text->replace(QTextEndOfFrame, '\n');
234
text->replace(QChar::ParagraphSeparator, '\n');
235
text->replace(QChar::LineSeparator, '\n');
236
text->replace(QChar::Nbsp, ' ');
240
\class QTextDocument qtextdocument.h
241
\brief The QTextDocument class holds formatted text that can be
242
viewed and edited using a QTextEdit.
247
QTextDocument is a container for structured rich text documents, providing
248
support for styled text and various types of document elements, such as
249
lists, tables, frames, and images.
250
They can be created for use in a QTextEdit, or used independently.
252
Each document element is described by an associated format object. Each
253
format object is treated as a unique object by QTextDocuments, and can be
254
passed to objectForFormat() to obtain the document element that it is
257
A QTextDocument can be edited programmatically using a QTextCursor, and
258
its contents can be examined by traversing the document structure. The
259
entire document structure is stored as a hierarchy of document elements
260
beneath the root frame, found with the rootFrame() function. Alternatively,
261
if you just want to iterate over the textual contents of the document you
262
can use begin(), end(), and findBlock() to retrieve text blocks that you
263
can examine and iterate over.
265
The layout of a document is determined by the documentLayout();
266
you can create your own QAbstractTextDocumentLayout subclass and
267
set it using setDocumentLayout() if you want to use your own
268
layout logic. The document's title can be obtained by calling the
269
documentTitle() function.
271
The toPlainText() and toHtml() convenience functions allow you to retrieve the
272
contents of the document as plain text and HTML.
273
The document's text can be searched using the find() functions.
275
Undo/redo of operations performed on the document can be controlled using
276
the setUndoRedoEnabled() function. The undo/redo system can be controlled
277
by an editor widget through the undo() and redo() slots; the document also
278
provides contentsChanged(), undoAvailable(), and redoAvailable() signals
279
that inform connected editor widgets about the state of the undo/redo
282
\sa QTextCursor QTextEdit \link richtext.html Rich Text Processing\endlink
286
\property QTextDocument::defaultFont
287
\brief the default font used to display the document's text
291
Constructs an empty QTextDocument with the given \a parent.
293
QTextDocument::QTextDocument(QObject *parent)
294
: QObject(*new QTextDocumentPrivate, parent)
301
Constructs a QTextDocument containing the plain (unformatted) \a text
302
specified, and with the given \a parent.
304
QTextDocument::QTextDocument(const QString &text, QObject *parent)
305
: QObject(*new QTextDocumentPrivate, parent)
309
QTextCursor(this).insertText(text);
313
Destroys the document.
315
QTextDocument::~QTextDocument()
321
Creates a new QTextDocument that is a copy of this text document. \a
322
parent is the parent of the returned text document.
324
QTextDocument *QTextDocument::clone(QObject *parent) const
326
QTextDocument *doc = new QTextDocument(parent);
327
QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
332
Returns true if the document is empty; otherwise returns false.
334
bool QTextDocument::isEmpty() const
336
Q_D(const QTextDocument);
337
/* because if we're empty we still have one single paragraph as
338
* one single fragment */
339
return d->length() <= 1;
345
void QTextDocument::clear()
352
Undoes the last editing operation on the document if
353
\link QTextDocument::isUndoAvailable() undo is available\endlink.
355
void QTextDocument::undo()
362
Redoes the last editing operation on the document if \link
363
QTextDocument::isRedoAvailable() redo is available\endlink.
365
void QTextDocument::redo()
374
Appends a custom undo \a item to the undo stack.
376
void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
379
d->appendUndoItem(item);
383
\property QTextDocument::undoRedoEnabled
384
\brief whether undo/redo are enabled for this document
386
This defaults to true. If disabled, the undo stack is cleared and
387
no items will be added to it.
389
void QTextDocument::setUndoRedoEnabled(bool enable)
392
d->enableUndoRedo(enable);
395
bool QTextDocument::isUndoRedoEnabled() const
397
Q_D(const QTextDocument);
398
return d->isUndoRedoEnabled();
402
\fn void QTextDocument::markContentsDirty(int position, int length)
404
Marks the contents specified by the given \a position and \a length
405
as "dirty", informing the document that it needs to be layed out
408
void QTextDocument::markContentsDirty(int from, int length)
411
d->documentChange(from, length);
415
\fn void QTextDocument::contentsChanged()
417
This signal is emitted whenever the document's content changes; for
418
example, when text is inserted or deleted, or when formatting is applied.
424
\fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
426
This signal is emitted whenever the document's content changes; for
427
example, when text is inserted or deleted, or when formatting is applied.
429
Information is provided about the \a position of the character in the
430
document where the change occurred, the number of characters removed
431
(\a charsRemoved), and the number of characters added (\a charsAdded).
433
The signal is emitted before the document's layout manager is notified
434
about the change. This hook allows you to implement syntax highlighting
437
\sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
442
\fn QTextDocument::undoAvailable(bool available);
444
This signal is emitted whenever undo operations become available
445
(\a available is true) or unavailable (\a available is false).
449
\fn QTextDocument::redoAvailable(bool available);
451
This signal is emitted whenever redo operations become available
452
(\a available is true) or unavailable (\a available is false).
456
\fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
458
This signal is emitted whenever the position of a cursor changed
459
due to an editing operation. The cursor that changed is passed in
464
Returns true is undo is available; otherwise returns false.
466
bool QTextDocument::isUndoAvailable() const
468
Q_D(const QTextDocument);
469
return d->isUndoAvailable();
473
Returns true is redo is available; otherwise returns false.
475
bool QTextDocument::isRedoAvailable() const
477
Q_D(const QTextDocument);
478
return d->isRedoAvailable();
482
Sets the document to use the given \a layout. The previous layout
485
void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
488
d->setLayout(layout);
492
Returns the document layout for this document.
494
QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
496
Q_D(const QTextDocument);
498
QTextDocument *that = const_cast<QTextDocument *>(this);
499
that->d_func()->setLayout(new QTextDocumentLayout(that));
506
Returns meta information about the document of the type specified by
509
\sa setMetaInformation()
511
QString QTextDocument::metaInformation(MetaInformation info) const
513
if (info != DocumentTitle)
515
Q_D(const QTextDocument);
516
return d->config()->title;
520
Sets the document's meta information of the type specified by \a info
521
to the given \a string.
523
\sa metaInformation()
525
void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
527
if (info != DocumentTitle)
530
d->config()->title = string;
534
Returns the plain text contained in the document. If you want
535
formatting information use a QTextCursor instead.
539
QString QTextDocument::toPlainText() const
541
Q_D(const QTextDocument);
542
QString txt = d->plainText();
543
qt_replace_special_text_characters(&txt);
548
Replaces the entire contents of the document with the given plain
553
void QTextDocument::setPlainText(const QString &text)
555
QTextDocumentFragment fragment = QTextDocumentFragment::fromPlainText(text);
556
QTextCursor cursor(this);
557
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
558
setUndoRedoEnabled(false);
559
cursor.insertFragment(fragment);
560
setUndoRedoEnabled(true);
564
Replaces the entire contents of the document with the given
565
HTML-formatted text in the \a html string.
567
The HTML formatting is respected as much as possible; for example,
568
"<b>bold</b> text" will produce text where the first word has a font
569
weight that gives it a bold appearance: "\bold{bold} text".
573
void QTextDocument::setHtml(const QString &html)
575
QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html);
576
QTextCursor cursor(this);
577
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
578
setUndoRedoEnabled(false);
579
cursor.insertFragment(fragment);
580
setUndoRedoEnabled(true);
584
\enum QTextDocument::FindFlag
586
This enum describes the options available to QTextDocument's find function. The options
587
can be OR-red together from the following list:
590
\value FindCaseSensitively By default find works case insensitive. Specifying this option
591
changes the behaviour to a case sensitive find operation.
592
\value FindWholeWords Makes find match only complete words.
596
\enum QTextDocument::MetaInformation
598
This enum describes the different types of meta information that can be
601
\value DocumentTitle The title of the document.
603
\sa metaInformation(), setMetaInformation()
606
static bool findInBlock(const QTextBlock &block, const QString &text, const QString &expression, int offset,
607
QTextDocument::FindFlags options, QTextCursor &cursor)
609
const Qt::CaseSensitivity cs = (options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive;
611
const int idx = (options & QTextDocument::FindBackward) ?
612
text.lastIndexOf(expression, offset, cs) : text.indexOf(expression, offset, cs);
616
if (options & QTextDocument::FindWholeWords) {
617
const int start = idx;
618
const int end = start + expression.length();
619
if ((start != 0 && text.at(start - 1).isLetterOrNumber())
620
|| (end != text.length() && text.at(end).isLetterOrNumber()))
624
cursor = QTextCursor(block.docHandle(), block.position() + idx);
625
cursor.setPosition(cursor.position() + expression.length(), QTextCursor::KeepAnchor);
630
\fn QTextCursor QTextDocument::find(const QString &expr, int position, FindFlags options) const
634
Finds the next occurrence of the string, \a expr, in the document.
635
The search starts at the given \a position, and proceeds forwards
636
through the document unless specified otherwise in the search options.
637
The \a options control the type of search performed.
639
Returns a cursor with the match selected if \a expr was found; otherwise
640
returns a null cursor.
642
If the \a position is 0 (the default) the search begins from the beginning
643
of the document; otherwise it begins at the specified position.
645
QTextCursor QTextDocument::find(const QString &expr, int from, FindFlags options) const
647
Q_D(const QTextDocument);
650
return QTextCursor();
655
QTextBlock block = d->blocksFind(pos);
657
if (!(options & FindBackward)) {
658
while (block.isValid()) {
659
int blockOffset = qMax(0, pos - block.position());
660
const QString blockText = block.text();
662
const int blockLength = block.length();
663
while (blockOffset < blockLength) {
664
if (findInBlock(block, blockText, expr, blockOffset, options, cursor))
667
blockOffset += expr.length();
670
block = block.next();
673
while (block.isValid()) {
674
int blockOffset = pos - block.position() - expr.size() - 1;
675
if (blockOffset > block.length())
676
blockOffset = block.length() - 1;
678
const QString blockText = block.text();
680
while (blockOffset >= 0) {
681
if (findInBlock(block, blockText, expr, blockOffset, options, cursor))
684
blockOffset -= expr.length();
687
block = block.previous();
691
return QTextCursor();
695
\fn QTextCursor QTextDocument::find(const QString &expr, const QTextCursor &cursor, FindFlags options) const
697
Finds the next occurrence of the string, \a expr, in the document.
698
The search starts at the position of the given \a cursor, and proceeds
699
forwards through the document unless specified otherwise in the search
700
options. The \a options control the type of search performed.
702
Returns a cursor with the match selected if \a expr was found; otherwise
703
returns a null cursor.
705
If the given \a cursor has a selection, the search begins after the
706
selection; otherwise it begins at the cursor's position.
708
By default the search is case-sensitive, and can match text anywhere in the
711
QTextCursor QTextDocument::find(const QString &expr, const QTextCursor &from, FindFlags options) const
713
const int pos = (from.isNull() ? 0 : from.selectionEnd());
714
return find(expr, pos, options);
720
\fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
722
Creates and returns a new document object (a QTextObject), based
723
on the given \a format.
725
QTextObjects will always get created through this method, so you
726
must reimplement it if you use custom text objects inside your document.
728
QTextObject *QTextDocument::createObject(const QTextFormat &f)
730
QTextObject *obj = 0;
731
if (f.isListFormat())
732
obj = new QTextList(this);
733
else if (f.isTableFormat())
734
obj = new QTextTable(this);
735
else if (f.isFrameFormat())
736
obj = new QTextFrame(this);
744
Returns the frame that contains the text cursor position \a pos.
746
QTextFrame *QTextDocument::frameAt(int pos) const
748
Q_D(const QTextDocument);
749
return d->frameAt(pos);
753
Returns the document's root frame.
755
QTextFrame *QTextDocument::rootFrame() const
757
Q_D(const QTextDocument);
758
return d->rootFrame();
762
Returns the text object associated with the given \a objectIndex.
764
QTextObject *QTextDocument::object(int objectIndex) const
766
Q_D(const QTextDocument);
767
return d->objectForIndex(objectIndex);
771
Returns the text object associated with the format \a f.
773
QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
775
Q_D(const QTextDocument);
776
return d->objectForFormat(f);
781
Returns the text block that contains the \a{pos}-th character.
783
QTextBlock QTextDocument::findBlock(int pos) const
785
Q_D(const QTextDocument);
786
return QTextBlock(docHandle(), d->blockMap().findNode(pos));
790
Returns the document's first text block.
792
QTextBlock QTextDocument::begin() const
794
Q_D(const QTextDocument);
795
return QTextBlock(docHandle(), d->blockMap().begin().n);
799
Returns the document's last text block.
801
QTextBlock QTextDocument::end() const
803
return QTextBlock(docHandle(), 0);
807
\property QTextDocument::pageSize
808
\brief the page size that should be used for layouting the document
810
\sa modificationChanged()
813
void QTextDocument::setPageSize(const QSizeF &size)
817
documentLayout()->documentChanged(0, 0, d->length());
820
QSizeF QTextDocument::pageSize() const
822
Q_D(const QTextDocument);
827
returns the number of pages in this document.
829
int QTextDocument::pageCount() const
831
return documentLayout()->pageCount();
835
Sets the default \a font to use in the document layout.
837
void QTextDocument::setDefaultFont(const QFont &font)
840
d->defaultFont = font;
841
documentLayout()->documentChanged(0, 0, d->length());
845
Returns the default font to be used in the document layout.
847
QFont QTextDocument::defaultFont() const
849
Q_D(const QTextDocument);
850
return d->defaultFont;
854
\fn QTextDocument::modificationChanged(bool changed)
856
This signal is emitted whenever the content of the document
857
changes in a way that affects the modification state. If \a
858
changed is true if the document has been modified; otherwise it is
861
For example calling setModified(false) on a document and then
862
inserting text causes the signal to get emitted. If you undo that
863
operation, causing the document to return to its original
864
unmodified state, the signal will get emitted again.
868
\property QTextDocument::modified
869
\brief whether the document has been modified by the user
871
\sa modificationChanged()
874
bool QTextDocument::isModified() const
876
return docHandle()->isModified();
879
void QTextDocument::setModified(bool m)
881
docHandle()->setModified(m);
885
Prints the document to the given \a printer. The QPrinter must be
886
set up before being used with this function.
888
This is only a convenience method to print the whole document to the printer.
890
void QTextDocument::print(QPrinter *printer) const
894
// Check that there is a valid device to print to.
895
if (!p.device()) return;
897
const int dpiy = p.device()->logicalDpiY();
898
const int margin = (int) ((2/2.54)*dpiy); // 2 cm margins
899
QRectF body(margin, margin, p.device()->width() - 2*margin, p.device()->height() - 2*margin);
901
QTextDocument *doc = clone();
902
QAbstractTextDocumentLayout *layout = doc->documentLayout();
903
QFont font(doc->defaultFont());
904
font.setPointSize(10); // we define 10pt to be a nice base size for printing
905
doc->setDefaultFont(font);
906
layout->setPaintDevice(printer);
907
doc->setPageSize(body.size());
909
QRectF view(0, 0, body.width(), body.height());
910
p.translate(body.left(), body.top());
914
QAbstractTextDocumentLayout::PaintContext ctx;
917
layout->draw(&p, ctx);
919
p.setClipping(false);
921
QString pageString = QString::number(page);
922
p.drawText(qRound(view.right() - p.fontMetrics().width(pageString)),
923
qRound(view.bottom() + p.fontMetrics().ascent() + 5*dpiy/72), pageString);
925
view.translate(0, body.height());
926
p.translate(0 , -body.height());
928
if (view.top() >= layout->documentSize().height())
939
\enum QTextDocument::ResourceType
941
This enum describes the types of resources that can be loaded by
942
QTextDocument's loadResource() function.
944
\value HtmlResource The resource contains HTML.
945
\value ImageResource The resource contains image data.
946
\value UserResource The first available value for user defined
953
Returns data of the specified \a type from the resource with the
956
This function is called by the rich text engine to request data that isn't
957
directly stored by QTextDocument, but still associated with it. For example,
958
images are referenced indirectly by the name attribute of a QTextImageFormat
961
Resources are cached internally in the document. If a resource can
962
not be found in the cache, loadResource is called to try to load
963
the resource. loadResource should then use addResource to add the
964
resource to the cache.
966
QVariant QTextDocument::resource(int type, const QUrl &name) const
968
Q_D(const QTextDocument);
969
QVariant r = d->resources.value(name);
971
r = const_cast<QTextDocument *>(this)->loadResource(type, name);
976
Adds the resource \a resource to the resource cache, using \a
977
type and \a name as identifiers.
979
void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
983
d->resources.insert(name, resource);
987
Loads data of the specified \a type from the resource with the
990
This function is called by the rich text engine to request data that isn't
991
directly stored by QTextDocument, but still associated with it. For example,
992
images are referenced indirectly by the name attribute of a QTextImageFormat
995
When called by Qt, \a type is one of the values of
996
QTextDocument::ResourceType.
998
If the QTextDocument is a child object of a QTextEdit, QTextBrowser,
999
or a QTextDocument itself then the default implementation tries
1000
to retrieve the data from the parent.
1002
QVariant QTextDocument::loadResource(int type, const QUrl &name)
1005
if (QTextDocument *doc = qobject_cast<QTextDocument *>(parent()))
1006
r = doc->loadResource(type, name);
1007
else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent()))
1008
r = edit->loadResource(type, name);
1010
addResource(type, name, r);
1014
static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
1016
QTextFormat diff = to;
1018
const QMap<int, QVariant> props = to.properties();
1019
for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
1021
if (it.value() == from.property(it.key()))
1022
diff.clearProperty(it.key());
1027
class QTextHtmlExporter
1030
QTextHtmlExporter(const QTextDocument *_doc);
1032
QString toHtml(const QByteArray &encoding);
1035
void emitFrame(QTextFrame::Iterator frameIt);
1036
void emitBlock(const QTextBlock &block);
1037
void emitTable(const QTextTable *table);
1038
void emitFragment(const QTextFragment &fragment);
1040
void emitBlockAttributes(const QTextBlock &block);
1041
bool emitCharFormatStyle(const QTextCharFormat &format);
1042
void emitTextLength(const char *attribute, const QTextLength &length);
1043
void emitAlignment(Qt::Alignment alignment);
1044
void emitFloatStyle(QTextFrameFormat::Position pos);
1045
void emitAttribute(const char *attribute, const QString &value);
1048
QTextCharFormat defaultCharFormat;
1049
const QTextDocument *doc;
1052
QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
1055
const QFont defaultFont = doc->defaultFont();
1056
defaultCharFormat.setFont(defaultFont);
1060
Returns the document in HTML format. The conversion may not be
1061
perfect, especially for complex documents, due to the limitations
1064
QString QTextHtmlExporter::toHtml(const QByteArray &encoding)
1066
html = QLatin1String("<html><head><meta name=\"qrichtext\" content=\"1\" />");
1068
if (!encoding.isEmpty())
1069
html += QString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding));
1071
QString title = doc->metaInformation(QTextDocument::DocumentTitle);
1072
if (!title.isEmpty())
1073
html += "<title>" + title + "</title>";
1074
html += QString("</head><body style=\" white-space: pre-wrap; font-family:%1; font-weight:%2; font-style:%3; text-decoration:none;\"")
1075
.arg(defaultCharFormat.fontFamily())
1076
.arg(defaultCharFormat.fontWeight() * 8)
1077
.arg(defaultCharFormat.fontItalic() ? "italic" : "normal");
1079
const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
1080
QBrush bg = fmt.background();
1081
if (bg != Qt::NoBrush)
1082
emitAttribute("bgcolor", bg.color().name());
1084
html += QLatin1Char('>');
1086
emitFrame(doc->rootFrame()->begin());
1087
html += QLatin1String("</body></html>");
1091
void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
1093
html += QLatin1Char(' ');
1095
html += QLatin1String("=\"");
1097
html += QLatin1Char('"');
1100
bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
1102
bool attributesEmitted = false;
1105
const QString family = format.fontFamily();
1106
if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
1107
html += QLatin1String(" font-family:");
1109
html += QLatin1Char(';');
1110
attributesEmitted = true;
1114
if (format.hasProperty(QTextFormat::FontPointSize)
1115
&& format.fontPointSize() != defaultCharFormat.fontPointSize()) {
1116
html += QLatin1String(" font-size:");
1117
html += QString::number(format.fontPointSize());
1118
html += QLatin1String("pt;");
1119
attributesEmitted = true;
1122
if (format.fontWeight() != defaultCharFormat.fontWeight()) {
1123
html += QLatin1String(" font-weight:");
1124
html += QString::number(format.fontWeight() * 8);
1125
html += QLatin1Char(';');
1126
attributesEmitted = true;
1129
if (format.fontItalic() != defaultCharFormat.fontItalic()) {
1130
html += QLatin1String(" font-style:");
1131
html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
1132
html += QLatin1Char(';');
1133
attributesEmitted = true;
1136
QLatin1String decorationTag(" text-decoration:");
1137
html += decorationTag;
1138
bool hasDecoration = false;
1139
bool atLeastOneDecorationSet = false;
1141
if (format.fontUnderline() != defaultCharFormat.fontUnderline()) {
1142
hasDecoration = true;
1143
if (format.fontUnderline()) {
1144
html += QLatin1String(" underline");
1145
atLeastOneDecorationSet = true;
1149
if (format.fontOverline() != defaultCharFormat.fontOverline()) {
1150
hasDecoration = true;
1151
if (format.fontOverline()) {
1152
html += QLatin1String(" overline");
1153
atLeastOneDecorationSet = true;
1157
if (format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
1158
hasDecoration = true;
1159
if (format.fontStrikeOut()) {
1160
html += QLatin1String(" line-through");
1161
atLeastOneDecorationSet = true;
1165
if (hasDecoration) {
1166
if (!atLeastOneDecorationSet)
1167
html += QLatin1String("none");
1168
html += QLatin1Char(';');
1169
attributesEmitted = true;
1171
html.chop(qstrlen(decorationTag.latin1()));
1174
if (format.foreground() != defaultCharFormat.foreground()) {
1175
html += QLatin1String(" color:");
1176
html += format.foreground().color().name();
1177
html += QLatin1Char(';');
1178
attributesEmitted = true;
1181
if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()) {
1182
html += QLatin1String(" vertical-align:");
1184
QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
1185
if (valign == QTextCharFormat::AlignSubScript)
1186
html += QLatin1String("sub");
1187
else if (valign == QTextCharFormat::AlignSuperScript)
1188
html += QLatin1String("super");
1190
html += QLatin1Char(';');
1191
attributesEmitted = true;
1194
return attributesEmitted;
1197
void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
1199
if (length.type() == QTextLength::VariableLength) // default
1202
html += QLatin1Char(' ');
1204
html += QLatin1String("=\"");
1205
html += QString::number(length.rawValue());
1207
if (length.type() == QTextLength::PercentageLength)
1208
html += QLatin1String("%\"");
1210
html += QLatin1String("\"");
1213
void QTextHtmlExporter::emitAlignment(Qt::Alignment alignment)
1215
switch (alignment & Qt::AlignHorizontal_Mask) {
1216
case Qt::AlignLeft: break;
1217
case Qt::AlignRight: html += QLatin1String(" align='right'"); break;
1218
case Qt::AlignHCenter: html += QLatin1String(" align='center'"); break;
1219
case Qt::AlignJustify: html += QLatin1String(" align='justify'"); break;
1223
void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos)
1225
if (pos == QTextFrameFormat::InFlow)
1228
html += QLatin1String(" style=\"float:");
1229
if (pos == QTextFrameFormat::FloatLeft)
1230
html += QLatin1String(" left;\"");
1231
else if (pos == QTextFrameFormat::FloatRight)
1232
html += QLatin1String(" right;\"");
1234
Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
1237
void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
1239
const QTextCharFormat format = fragment.charFormat();
1241
if (format.hasProperty(QTextFormat::DocumentFragmentMark)
1242
&& (format.intProperty(QTextFormat::DocumentFragmentMark) & QTextDocumentFragmentPrivate::FragmentStart))
1243
html += QLatin1String("<!--StartFragment-->");
1245
bool closeAnchor = false;
1247
if (format.isAnchor()) {
1248
const QString name = format.anchorName();
1249
if (!name.isEmpty()) {
1250
html += QLatin1String("<a name=\"");
1252
html += QLatin1String("\"></a>");
1254
const QString href = format.anchorHref();
1255
if (!href.isEmpty()) {
1256
html += QLatin1String("<a href=\"");
1258
html += QLatin1String("\">");
1263
QLatin1String styleTag("<span style=\"");
1266
const bool attributesEmitted = emitCharFormatStyle(format);
1267
if (attributesEmitted)
1268
html += QLatin1String("\">");
1270
html.chop(qstrlen(styleTag.latin1()));
1272
QString txt = fragment.text();
1273
if (txt.count() == 1 && txt.at(0) == QChar::ObjectReplacementCharacter) {
1274
if (format.isImageFormat()) {
1275
QTextImageFormat imgFmt = format.toImageFormat();
1277
html += QLatin1String("<img");
1279
if (imgFmt.hasProperty(QTextFormat::ImageName))
1280
emitAttribute("src", imgFmt.name());
1282
if (imgFmt.hasProperty(QTextFormat::ImageWidth))
1283
emitAttribute("width", QString::number(imgFmt.width()));
1285
if (imgFmt.hasProperty(QTextFormat::ImageHeight))
1286
emitAttribute("height", QString::number(imgFmt.height()));
1288
if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
1289
emitFloatStyle(imageFrame->frameFormat().position());
1291
html += QLatin1String(" />");
1294
Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
1296
txt = Qt::escape(txt);
1298
// split for [\n{LineSeparator}]
1299
QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
1300
forcedLineBreakRegExp[3] = QChar::LineSeparator;
1302
const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp));
1303
for (int i = 0; i < lines.count(); ++i) {
1305
html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
1306
html += lines.at(i);
1310
if (attributesEmitted)
1311
html += QLatin1String("</span>");
1314
html += QLatin1String("</a>");
1316
if (format.hasProperty(QTextFormat::DocumentFragmentMark)
1317
&& (format.intProperty(QTextFormat::DocumentFragmentMark) & QTextDocumentFragmentPrivate::FragmentEnd))
1318
html += QLatin1String("<!--EndFragment-->");
1321
static bool isOrderedList(int style)
1323
return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
1324
|| style == QTextListFormat::ListUpperAlpha;
1327
void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
1329
QTextBlockFormat format = block.blockFormat();
1330
emitAlignment(format.alignment());
1332
Qt::LayoutDirection dir = format.layoutDirection();
1333
if (dir == Qt::LeftToRight) {
1334
// assume default to not bloat the html too much
1335
// html += QLatin1String(" dir='ltr'");
1337
html += QLatin1String(" dir='rtl'");
1340
QLatin1String style(" style=\"");
1343
if (block.begin().atEnd()) {
1344
html += "-qt-paragraph-type:empty;";
1347
html += QLatin1String(" margin-top:");
1348
html += QString::number(format.topMargin());
1349
html += QLatin1String("px;");
1351
html += QLatin1String(" margin-bottom:");
1352
html += QString::number(format.bottomMargin());
1353
html += QLatin1String("px;");
1355
html += QLatin1String(" margin-left:");
1356
html += QString::number(format.leftMargin());
1357
html += QLatin1String("px;");
1359
html += QLatin1String(" margin-right:");
1360
html += QString::number(format.rightMargin());
1361
html += QLatin1String("px;");
1363
html += QLatin1String(" -qt-block-indent:");
1364
html += QString::number(format.indent());
1365
html += QLatin1Char(';');
1367
html += QLatin1String(" text-indent:");
1368
html += QString::number(format.indent());
1369
html += QLatin1String("px;");
1371
// ### 'if' needed as long as the block char format of a block at pos == 0
1372
// is equivalent to the char format at that position.
1373
// later on in the piecetable that's not the case, that's when the block char
1374
// fmt is at block.position() - 1
1375
// When changing this also change the 'if' in emitBlock
1376
if (block.position() > 0) {
1377
QTextCharFormat diff = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
1378
if (!diff.properties().isEmpty())
1379
emitCharFormatStyle(diff);
1382
html += QLatin1Char('"');
1384
QBrush bg = format.background();
1385
if (bg != Qt::NoBrush)
1386
emitAttribute("bgcolor", bg.color().name());
1389
void QTextHtmlExporter::emitBlock(const QTextBlock &block)
1391
if (block.begin().atEnd()) {
1392
// ### HACK, remove once QTextFrame::Iterator is fixed
1393
int p = block.position();
1396
QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
1397
QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
1398
if (ch == QTextBeginningOfFrame
1399
|| ch == QTextEndOfFrame)
1403
// save and later restore, in case we 'change' the default format by
1404
// emitting block char format information
1405
QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
1407
QTextList *list = block.textList();
1409
if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
1410
const QTextListFormat format = list->format();
1411
const int style = format.style();
1413
case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
1414
case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
1415
case QTextListFormat::ListCircle: html += QLatin1String("<ul type=circle"); break;
1416
case QTextListFormat::ListSquare: html += QLatin1String("<ul type=square"); break;
1417
case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=a"); break;
1418
case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=A"); break;
1419
default: html += QLatin1String("<ul"); // ### should not happen
1422
if (format.hasProperty(QTextFormat::ListIndent)) {
1423
html += QLatin1String(" style=\"-qt-list-indent: ");
1424
html += QString::number(format.indent());
1425
html += QLatin1String(";\"");
1428
html += QLatin1Char('>');
1431
html += QLatin1String("<li");
1433
const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
1434
if (!blockFmt.properties().isEmpty()) {
1435
html += QLatin1String(" style=\"");
1436
emitCharFormatStyle(blockFmt);
1437
html += QLatin1Char('\"');
1439
defaultCharFormat.merge(block.charFormat());
1443
const bool pre = block.blockFormat().nonBreakableLines();
1446
html += QLatin1Char('>');
1447
html += QLatin1String("<pre");
1449
html += QLatin1String("<p");
1452
emitBlockAttributes(block);
1454
html += QLatin1Char('>');
1456
// ### 'if' needed as long as the block char format of a block at pos == 0
1457
// is equivalent to the char format at that position.
1458
// later on in the piecetable that's not the case, that's when the block char
1459
// fmt is at block.position() - 1
1460
// When changing this also change the 'if' in emitBlockAttributes
1461
if (block.position() > 0) {
1462
defaultCharFormat.merge(block.charFormat());
1465
for (QTextBlock::Iterator it = block.begin();
1467
emitFragment(it.fragment());
1470
html += QLatin1String("</pre>");
1472
html += QLatin1String("</p>");
1475
if (list->itemNumber(block) == list->count() - 1) { // last item? close list
1476
if (isOrderedList(list->format().style()))
1477
html += QLatin1String("</ol>");
1479
html += QLatin1String("</ul>");
1483
defaultCharFormat = oldDefaultCharFormat;
1486
void QTextHtmlExporter::emitTable(const QTextTable *table)
1488
QTextTableFormat format = table->format();
1490
html += QLatin1String("<table");
1492
if (format.hasProperty(QTextFormat::FrameBorder))
1493
emitAttribute("border", QString::number(format.border()));
1495
emitFloatStyle(format.position());
1496
emitAlignment(format.alignment());
1497
emitTextLength("width", format.width());
1499
if (format.hasProperty(QTextFormat::TableCellSpacing))
1500
emitAttribute("cellspacing", QString::number(format.cellSpacing()));
1501
if (format.hasProperty(QTextFormat::TableCellPadding))
1502
emitAttribute("cellpadding", QString::number(format.cellPadding()));
1504
QBrush bg = format.background();
1505
if (bg != Qt::NoBrush)
1506
emitAttribute("bgcolor", bg.color().name());
1508
html += QLatin1Char('>');
1510
const int rows = table->rows();
1511
const int columns = table->columns();
1513
QVector<QTextLength> columnWidths = format.columnWidthConstraints();
1514
if (columnWidths.isEmpty()) {
1515
columnWidths.resize(columns);
1516
columnWidths.fill(QTextLength());
1518
Q_ASSERT(columnWidths.count() == columns);
1520
QVarLengthArray<bool> widthEmittedForColumn(columns);
1521
for (int i = 0; i < columns; ++i)
1522
widthEmittedForColumn[i] = false;
1524
for (int row = 0; row < rows; ++row) {
1525
html += QLatin1String("<tr>");
1527
for (int col = 0; col < columns; ++col) {
1528
const QTextTableCell cell = table->cellAt(row, col);
1531
if (cell.row() != row)
1534
if (cell.column() != col)
1537
html += QLatin1String("<td");
1539
if (!widthEmittedForColumn[col]) {
1540
emitTextLength("width", columnWidths.at(col));
1541
widthEmittedForColumn[col] = true;
1544
if (cell.columnSpan() > 1)
1545
emitAttribute("colspan", QString::number(cell.columnSpan()));
1547
if (cell.rowSpan() > 1)
1548
emitAttribute("rowspan", QString::number(cell.rowSpan()));
1550
const QTextCharFormat cellFormat = cell.format();
1551
QBrush bg = cellFormat.background();
1552
if (bg != Qt::NoBrush)
1553
emitAttribute("bgcolor", bg.color().name());
1555
html += QLatin1Char('>');
1557
emitFrame(cell.begin());
1559
html += QLatin1String("</td>");
1562
html += QLatin1String("</tr>");
1565
html += QLatin1String("</table>");
1568
void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt)
1570
if (!frameIt.atEnd()) {
1571
QTextFrame::Iterator next = frameIt;
1574
&& frameIt.currentFrame() == 0
1575
&& frameIt.parentFrame() != doc->rootFrame()
1576
&& frameIt.currentBlock().begin().atEnd())
1580
for (QTextFrame::Iterator it = frameIt;
1581
!it.atEnd(); ++it) {
1582
if (QTextTable *table = qobject_cast<QTextTable *>(it.currentFrame()))
1584
else if (it.currentBlock().isValid())
1585
emitBlock(it.currentBlock());
1590
Returns a string containing an HTML representation of the document.
1592
The \a encoding parameter specifies the value for the charset attribute
1593
in the html header. For example if 'utf-8' is specified then the
1594
beginning of the generated html will look like this:
1596
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>...
1599
If no encoding is specified then no such meta information is generated.
1601
If you later on convert the returned html string into a byte array for
1602
transmission over a network or when saving to disk you should specify
1603
the encoding you're going to use for the conversion to a byte array here.
1605
QString QTextDocument::toHtml(const QByteArray &encoding) const
1607
return QTextHtmlExporter(this).toHtml(encoding);
1611
Returns a vector of text formats for all the formats used in the document.
1613
QVector<QTextFormat> QTextDocument::allFormats() const
1615
Q_D(const QTextDocument);
1616
return d->formatCollection()->formats;
1623
So that not all classes have to be friends of each other...
1625
QTextDocumentPrivate *QTextDocument::docHandle() const
1627
Q_D(const QTextDocument);
1628
return const_cast<QTextDocumentPrivate *>(d);