qt6windows7/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp

3970 lines
135 KiB
C++
Raw Normal View History

2023-10-30 06:33:08 +08:00
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QSignalSpy>
#include <qtextdocument.h>
#include <qdebug.h>
#include <qtextcursor.h>
#include <qtextdocumentfragment.h>
#include <qtextformat.h>
#include <qtextobject.h>
#include <qtexttable.h>
#include <qabstracttextdocumentlayout.h>
#include <qtextlist.h>
#include <qguiapplication.h>
#include <qurl.h>
#include <qpainter.h>
#include <qfontmetrics.h>
#include <qimage.h>
#include <qtextlayout.h>
#include <QDomDocument>
#include "common.h"
// #define DEBUG_WRITE_OUTPUT
QT_FORWARD_DECLARE_CLASS(QTextDocument)
class tst_QTextDocument : public QObject
{
Q_OBJECT
public:
tst_QTextDocument();
private slots:
void init();
void cleanup();
void cleanupTestCase();
void getSetCheck();
void isEmpty();
void find_data();
void find();
void find2();
void findWithRegularExpression_data();
void findWithRegularExpression();
void findMultiple();
void basicIsModifiedChecks();
void moreIsModified();
void isModified2();
void isModified3();
void isModified4();
void noundo_basicIsModifiedChecks();
void noundo_moreIsModified();
void noundo_isModified2();
void noundo_isModified3();
void mightBeRichText();
void mightBeRichText_data();
void task240325();
void preFont();
void stylesheetFont_data();
void stylesheetFont();
void toHtml_data();
void toHtml();
void toHtml2();
void setFragmentMarkersInHtmlExport();
void setMediaRule();
void toHtmlBodyBgColor();
void toHtmlBodyBgColorRgba();
void toHtmlBodyBgColorTransparent();
void toHtmlRootFrameProperties();
void toHtmlLineHeightProperties();
void toHtmlDefaultFontSpacingProperties();
void toHtmlTextDecorationUnderline();
void capitalizationHtmlInExport();
void wordspacingHtmlExport();
void cursorPositionChanged();
void cursorPositionChangedOnSetText();
void textFrameIterator();
void markContentsDirty();
void clonePreservesMetaInformation();
void clonePreservesPageSize();
void clonePreservesPageBreakPolicies();
void clonePreservesDefaultFont();
void clonePreservesRootFrameFormat();
void clonePreservesResources();
void clonePreservesUserStates();
void clonePreservesIndentWidth();
void clonePreservesFormatsWhenEmpty();
void blockCount();
void defaultStyleSheet();
void defaultTableStyle_data();
void defaultTableStyle();
void resolvedFontInEmptyFormat();
void defaultRootFrameMargin();
void clearResources();
void setPlainText();
void toPlainText_data();
void toPlainText();
void toRawText();
void deleteTextObjectsOnClear();
void maximumBlockCount();
void adjustSize();
void initialUserData();
void html_defaultFont();
void blockCountChanged();
void nonZeroDocumentLengthOnClear();
void setTextPreservesUndoRedoEnabled();
void firstLast();
void backgroundImage_toHtml();
void backgroundImage_toHtml2();
void backgroundImage_clone();
void backgroundImage_copy();
void documentCleanup();
void characterAt();
void revisions();
void revisionWithUndoCompressionAndUndo();
void testUndoCommandAdded();
void testUndoBlocks();
void receiveCursorPositionChangedAfterContentsChange();
void copiedFontSize();
void QTBUG25778_pixelSizeFromHtml();
void htmlExportImportBlockCount();
void QTBUG27354_spaceAndSoftSpace();
void baseUrl_data();
void baseUrl();
void QTBUG28998_linkColor();
void textCursorUsageWithinContentsChange();
void cssInheritance();
void lineHeightType();
void cssLineHeightMultiplier();
void fontTagFace();
void clearUndoRedoStacks();
void mergeFontFamilies();
void resourceProvider();
void contentsChangeIndices_data();
void contentsChangeIndices();
void insertHtmlWithComments_data();
void insertHtmlWithComments();
void delayedLayout();
private:
void backgroundImage_checkExpectedHtml(const QTextDocument &doc);
void buildRegExpData();
static QString cssFontSizeString(const QFont &font);
void writeActualAndExpected(const char* testTag, const QString &actual, const QString &expected);
QTextDocument *doc;
QTextCursor cursor;
QFont defaultFont;
QString htmlHead;
QString htmlTail;
};
class MyAbstractTextDocumentLayout : public QAbstractTextDocumentLayout
{
public:
MyAbstractTextDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc) {}
void draw(QPainter *, const PaintContext &) override {}
int hitTest(const QPointF &, Qt::HitTestAccuracy) const override { return 0; }
int pageCount() const override { return 0; }
QSizeF documentSize() const override { return QSizeF(); }
QRectF frameBoundingRect(QTextFrame *) const override { return QRectF(); }
QRectF blockBoundingRect(const QTextBlock &) const override { return QRectF(); }
void documentChanged(int, int, int) override {}
};
QString tst_QTextDocument::cssFontSizeString(const QFont &font)
{
return font.pointSize() >= 0
? QString::number(font.pointSizeF()) + QStringLiteral("pt")
: QString::number(font.pixelSize()) + QStringLiteral("px");
}
void tst_QTextDocument::writeActualAndExpected(const char *testTag, const QString &actual, const QString &expected)
{
#ifdef DEBUG_WRITE_OUTPUT
{
QFile out(QDir::temp().absoluteFilePath(QLatin1String(testTag) + QLatin1String("-actual.html")));
out.open(QFile::WriteOnly);
out.write(actual.toUtf8());
out.close();
} {
QFile out(QDir::temp().absoluteFilePath(QLatin1String(testTag) + QLatin1String("-expected.html")));
out.open(QFile::WriteOnly);
out.write(expected.toUtf8());
out.close();
}
#else
Q_UNUSED(testTag);
Q_UNUSED(actual);
Q_UNUSED(expected);
#endif
}
// Testing get/set functions
void tst_QTextDocument::getSetCheck()
{
QTextDocument obj1;
// QAbstractTextDocumentLayout * QTextDocument::documentLayout()
// void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *)
QPointer<MyAbstractTextDocumentLayout> var1 = new MyAbstractTextDocumentLayout(0);
obj1.setDocumentLayout(var1);
QCOMPARE(static_cast<QAbstractTextDocumentLayout *>(var1), obj1.documentLayout());
obj1.setDocumentLayout((QAbstractTextDocumentLayout *)0);
QVERIFY(var1.isNull());
QVERIFY(obj1.documentLayout());
// bool QTextDocument::useDesignMetrics()
// void QTextDocument::setUseDesignMetrics(bool)
obj1.setUseDesignMetrics(false);
QCOMPARE(false, obj1.useDesignMetrics());
obj1.setUseDesignMetrics(true);
QCOMPARE(true, obj1.useDesignMetrics());
}
tst_QTextDocument::tst_QTextDocument()
{
QImage img(16, 16, QImage::Format_ARGB32_Premultiplied);
img.save("foo.png");
}
void tst_QTextDocument::init()
{
doc = new QTextDocument;
cursor = QTextCursor(doc);
defaultFont = QFont();
htmlHead = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"hr { height: 1px; border-width: 0; }\n"
"li.unchecked::marker { content: \"\\2610\"; }\n"
"li.checked::marker { content: \"\\2612\"; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\">\n");
htmlHead = htmlHead.arg(defaultFont.family())
.arg(cssFontSizeString(defaultFont))
.arg(defaultFont.weight())
.arg((defaultFont.italic() ? "italic" : "normal"));
htmlTail = QString("</body></html>");
}
void tst_QTextDocument::cleanup()
{
cursor = QTextCursor();
delete doc;
doc = 0;
}
void tst_QTextDocument::cleanupTestCase()
{
QFile::remove(QLatin1String("foo.png"));
}
void tst_QTextDocument::isEmpty()
{
QVERIFY(doc->isEmpty());
}
void tst_QTextDocument::find_data()
{
QTest::addColumn<QString>("haystack");
QTest::addColumn<QString>("needle");
QTest::addColumn<int>("flags");
QTest::addColumn<int>("from");
QTest::addColumn<int>("anchor");
QTest::addColumn<int>("position");
QTest::newRow("1") << "Hello World" << "World" << int(QTextDocument::FindCaseSensitively) << 0 << 6 << 11;
QTest::newRow("2") << QString::fromLatin1("Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1("World")
<< "World" << int(QTextDocument::FindCaseSensitively) << 1 << 6 << 11;
QTest::newRow("3") << QString::fromLatin1("Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1("World")
<< "Hello" << int(QTextDocument::FindCaseSensitively | QTextDocument::FindBackward) << 10 << 0 << 5;
QTest::newRow("4wholewords") << QString::fromLatin1("Hello Blah World")
<< "Blah" << int(QTextDocument::FindWholeWords) << 0 << 6 << 10;
QTest::newRow("5wholewords") << QString::fromLatin1("HelloBlahWorld")
<< "Blah" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1;
QTest::newRow("6wholewords") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "Blah" << int(QTextDocument::FindWholeWords) << 0 << 15 << 19;
QTest::newRow("7wholewords") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "Blah" << int(QTextDocument::FindWholeWords | QTextDocument::FindBackward) << 23 << 15 << 19;
QTest::newRow("8wholewords") << QString::fromLatin1("Hello: World\n")
<< "orld" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1;
QTest::newRow("across-paragraphs") << QString::fromLatin1("First Parag\nSecond Parag with a lot more text")
<< "Parag" << int(QTextDocument::FindBackward)
<< 15 << 6 << 11;
QTest::newRow("nbsp") << "Hello" + QString(QChar(QChar::Nbsp)) +"World" << " " << int(QTextDocument::FindCaseSensitively) << 0 << 5 << 6;
QTest::newRow("from-the-end") << "Hello World" << "Hello World" << int(QTextDocument::FindCaseSensitively| QTextDocument::FindBackward) << 11 << 0 << 11;
QTest::newRow("bw-cross-paras-1") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 7 << 3 << 4;
QTest::newRow("bw-cross-paras-2") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 6 << 3 << 4;
QTest::newRow("bw-cross-paras-3") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 5 << 3 << 4;
QTest::newRow("bw-cross-paras-4") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 3 << 0 << 1;
QTest::newRow("bw-cross-paras-5") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 5 << 1 << 2;
QTest::newRow("bw-cross-paras-6") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 4 << 1 << 2;
QTest::newRow("bw-cross-paras-7") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 3 << 1 << 2;
QTest::newRow("bw-cross-paras-8") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 2 << 1 << 2;
}
void tst_QTextDocument::find()
{
QFETCH(QString, haystack);
QFETCH(QString, needle);
QFETCH(int, flags);
QFETCH(int, from);
QFETCH(int, anchor);
QFETCH(int, position);
cursor.insertText(haystack);
cursor = doc->find(needle, from, QTextDocument::FindFlags(flags));
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
//search using a regular expression
QRegularExpression expr(QRegularExpression::escape(needle));
QTextDocument::FindFlags flg(flags);
cursor = doc->find(expr, from, flg);
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
}
void tst_QTextDocument::findWithRegularExpression_data()
{
buildRegExpData();
}
void tst_QTextDocument::findWithRegularExpression()
{
QFETCH(QString, haystack);
QFETCH(QString, needle);
QFETCH(int, flags);
QFETCH(int, from);
QFETCH(int, anchor);
QFETCH(int, position);
cursor.insertText(haystack);
//search using a regular expression
QRegularExpression expr(needle);
QTextDocument::FindFlags flg(flags);
cursor = doc->find(expr, from, flg);
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
}
void tst_QTextDocument::find2()
{
doc->setPlainText("aaa");
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
QTextCursor hit = doc->find("a", cursor);
QCOMPARE(hit.position(), 2);
QCOMPARE(hit.anchor(), 1);
}
void tst_QTextDocument::findMultiple()
{
const QString text("foo bar baz foo bar baz");
doc->setPlainText(text);
cursor.movePosition(QTextCursor::Start);
cursor = doc->find("bar", cursor);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find("bar", cursor);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor.movePosition(QTextCursor::End);
cursor = doc->find("bar", cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find("bar", cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
QRegularExpression regularExpression("bar");
cursor.movePosition(QTextCursor::End);
cursor = doc->find(regularExpression, cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find(regularExpression, cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor.movePosition(QTextCursor::Start);
cursor = doc->find(regularExpression, cursor);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find(regularExpression, cursor);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
}
void tst_QTextDocument::basicIsModifiedChecks()
{
QSignalSpy spy(doc, SIGNAL(modificationChanged(bool)));
QVERIFY(!doc->isModified());
cursor.insertText("Hello World");
QVERIFY(doc->isModified());
QCOMPARE(spy.size(), 1);
QVERIFY(spy.takeFirst().at(0).toBool());
doc->undo();
QVERIFY(!doc->isModified());
QCOMPARE(spy.size(), 1);
QVERIFY(!spy.takeFirst().at(0).toBool());
doc->redo();
QVERIFY(doc->isModified());
QCOMPARE(spy.size(), 1);
QVERIFY(spy.takeFirst().at(0).toBool());
}
void tst_QTextDocument::moreIsModified()
{
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
doc->undo();
QVERIFY(!doc->isModified());
}
void tst_QTextDocument::isModified2()
{
// reported on qt4-preview-feedback
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->setModified(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
}
void tst_QTextDocument::isModified3()
{
QVERIFY(!doc->isModified());
doc->setUndoRedoEnabled(false);
doc->setUndoRedoEnabled(true);
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
}
void tst_QTextDocument::isModified4()
{
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
cursor.insertText("World");
doc->setModified(false);
QVERIFY(!doc->isModified());
cursor.insertText("Again");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
doc->redo();
QVERIFY(!doc->isModified());
doc->redo();
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
//task 197769
cursor.insertText("Hello");
QVERIFY(doc->isModified());
}
void tst_QTextDocument::noundo_basicIsModifiedChecks()
{
doc->setUndoRedoEnabled(false);
QSignalSpy spy(doc, SIGNAL(modificationChanged(bool)));
QVERIFY(!doc->isModified());
cursor.insertText("Hello World");
QVERIFY(doc->isModified());
QCOMPARE(spy.size(), 1);
QVERIFY(spy.takeFirst().at(0).toBool());
doc->undo();
QVERIFY(doc->isModified());
QCOMPARE(spy.size(), 0);
doc->redo();
QVERIFY(doc->isModified());
QCOMPARE(spy.size(), 0);
}
void tst_QTextDocument::task240325()
{
doc->setHtml("<html><img width=\"100\" height=\"100\" align=\"right\"/>Foobar Foobar Foobar Foobar</html>");
QImage img(1000, 7000, QImage::Format_ARGB32_Premultiplied);
QPainter p(&img);
QFontMetrics fm(p.font());
// Set page size to contain image and one "Foobar"
doc->setPageSize(QSize(100 + fm.horizontalAdvance("Foobar")*2, 1000));
// Force layout
doc->drawContents(&p);
QCOMPARE(doc->blockCount(), 1);
for (QTextBlock block = doc->begin() ; block!=doc->end() ; block = block.next()) {
QTextLayout *layout = block.layout();
QCOMPARE(layout->lineCount(), 4);
for (int lineIdx=0;lineIdx<layout->lineCount();++lineIdx) {
QTextLine line = layout->lineAt(lineIdx);
QString text = block.text().mid(line.textStart(), line.textLength()).trimmed();
// Remove start token
if (lineIdx == 0)
text = text.mid(1);
QCOMPARE(text, QString::fromLatin1("Foobar"));
}
}
}
void tst_QTextDocument::stylesheetFont_data()
{
QTest::addColumn<QString>("stylesheet");
QTest::addColumn<QFont>("font");
{
QFont font;
font.setBold(true);
font.setPixelSize(64);
QTest::newRow("Regular font specification")
<< "font-size: 64px; font-weight: bold;"
<< font;
}
{
QFont font;
font.setBold(true);
font.setPixelSize(64);
QTest::newRow("Shorthand font specification")
<< "font: normal bold 64px Arial;"
<< font;
}
}
void tst_QTextDocument::stylesheetFont()
{
QFETCH(QString, stylesheet);
QFETCH(QFont, font);
QString html = QString::fromLatin1("<html>"
"<body>"
"<div style=\"%1\" >"
"Foobar"
"</div>"
"</body>"
"</html>").arg(stylesheet);
qDebug() << html;
doc->setHtml(html);
QCOMPARE(doc->blockCount(), 1);
// First and only block
QTextBlock block = doc->firstBlock();
QString text = block.text();
QCOMPARE(text, QString::fromLatin1("Foobar"));
QFont actualFont = block.charFormat().font();
QCOMPARE(actualFont.bold(), font.bold());
QCOMPARE(actualFont.pixelSize(), font.pixelSize());
}
void tst_QTextDocument::preFont()
{
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
const QString html = QString::fromLatin1( "<html>"
"<body>"
"<pre>"
"Foobar"
"</pre>"
"</body>"
"</html>");
doc->setHtml(html);
QCOMPARE(doc->blockCount(), 1);
// First and only block
QTextBlock block = doc->firstBlock();
QString text = block.text();
QCOMPARE(text, QString::fromLatin1("Foobar"));
QFont actualFont = block.charFormat().font();
QCOMPARE(actualFont.family(), font.family());
}
void tst_QTextDocument::noundo_moreIsModified()
{
doc->setUndoRedoEnabled(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
cursor.insertText("Hello");
doc->undo();
QVERIFY(doc->isModified());
}
void tst_QTextDocument::noundo_isModified2()
{
// reported on qt4-preview-feedback
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->setModified(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
}
void tst_QTextDocument::noundo_isModified3()
{
doc->setUndoRedoEnabled(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
}
void tst_QTextDocument::mightBeRichText_data()
{
const char qtDocuHeader[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
"<!DOCTYPE html\n"
" PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
QVERIFY(Qt::mightBeRichText(QString::fromLatin1(qtDocuHeader)));
QTest::addColumn<QString>("input");
QTest::addColumn<bool>("result");
QTest::newRow("documentation-header") << QString("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
"<!DOCTYPE html\n"
" PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">")
<< true;
QTest::newRow("br-nospace") << QString("Test <br/> new line") << true;
QTest::newRow("br-space") << QString("Test <br /> new line") << true;
QTest::newRow("br-invalidspace") << QString("Test <br/ > new line") << false;
QTest::newRow("invalid closing tag") << QString("Test <br/ line") << false;
}
void tst_QTextDocument::mightBeRichText()
{
QFETCH(QString, input);
QFETCH(bool, result);
QCOMPARE(result, Qt::mightBeRichText(input));
}
Q_DECLARE_METATYPE(QTextDocumentFragment)
#define CREATE_DOC_AND_CURSOR() \
QTextDocument doc; \
doc.setDefaultFont(defaultFont); \
QTextCursor cursor(&doc);
void tst_QTextDocument::toHtml_data()
{
QTest::addColumn<QTextDocumentFragment>("input");
QTest::addColumn<QString>("expectedOutput");
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTest::newRow("simple") << QTextDocumentFragment(&doc) << QString("<p DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("&<>");
QTest::newRow("entities") << QTextDocumentFragment(&doc) << QString("<p DEFAULTBLOCKSTYLE>&amp;&lt;&gt;</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamilies({QLatin1String("Times")});
cursor.insertText("Blah", fmt);
QTest::newRow("font-family") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Times';\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamilies({QLatin1String("Foo's Family")});
cursor.insertText("Blah", fmt);
QTest::newRow("font-family-with-quotes1") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:&quot;Foo's Family&quot;;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamilies({QLatin1String("Foo\"s Family")});
cursor.insertText("Blah", fmt);
QTest::newRow("font-family-with-quotes2") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Foo&quot;s Family';\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamilies(QStringList{ "Times", "serif" });
cursor.insertText("Blah", fmt);
QTest::newRow("font-family-with-fallback") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Times','serif';\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setNonBreakableLines(true);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("pre") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<pre DEFAULTBLOCKSTYLE>Blah</pre>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontPointSize(40);
cursor.insertText("Blah", fmt);
QTest::newRow("font-size") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-size:40pt;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setProperty(QTextFormat::FontSizeIncrement, 2);
cursor.insertText("Blah", fmt);
QTest::newRow("logical-font-size") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-size:x-large;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Foo");
QTextCharFormat fmt;
fmt.setFontPointSize(40);
cursor.insertBlock(QTextBlockFormat(), fmt);
fmt.clearProperty(QTextFormat::FontPointSize);
cursor.insertText("Blub", fmt);
QTest::newRow("no-font-size") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE>Foo</p>\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blub</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setLayoutDirection(Qt::RightToLeft);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("rtl") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p dir='rtl' DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignJustify);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockalign") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p align=\"justify\" DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignCenter);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockalign2") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p align=\"center\" DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockalign3") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p align=\"right\" DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor("#0000ff"));
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("bgcolor") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:#0000ff;\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor(255, 0, 0, 51));
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("bgcolor-rgba") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:rgba(255,0,0,0.2);\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor(255, 0, 0, 0));
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("bgcolor-transparent") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:transparent;\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontWeight(320);
cursor.insertText("Blah", fmt);
QTest::newRow("font-weight") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-weight:320;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontItalic(true);
cursor.insertText("Blah", fmt);
QTest::newRow("font-italic") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-style:italic;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontUnderline(true);
fmt.setFontOverline(false);
cursor.insertText("Blah", fmt);
QTest::newRow("text-decoration-1") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" text-decoration: underline;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
cursor.insertText("Blah", fmt);
QTest::newRow("color") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" color:#00ff00;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor(0, 255, 0, 51));
cursor.insertText("Blah", fmt);
QTest::newRow("color-rgba") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" color:rgba(0,255,0,0.2);\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor(0, 255, 0, 0));
cursor.insertText("Blah", fmt);
QTest::newRow("color-transparent") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" color:transparent;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setBackground(QColor("#00ff00"));
cursor.insertText("Blah", fmt);
QTest::newRow("span-bgcolor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setBackground(QColor(0, 255, 0, 51));
cursor.insertText("Blah", fmt);
QTest::newRow("span-bgcolor-rgba") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:rgba(0,255,0,0.2);\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setBackground(QColor(0, 255, 0, 0));
cursor.insertText("Blah", fmt);
QTest::newRow("span-bgcolor-transparent") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:transparent;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setVerticalAlignment(QTextCharFormat::AlignSubScript);
cursor.insertText("Blah", fmt);
QTest::newRow("valign-sub") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" vertical-align:sub;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
cursor.insertText("Blah", fmt);
QTest::newRow("valign-super") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" vertical-align:super;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorNames({"blub"});
cursor.insertText("Blah", fmt);
QTest::newRow("named anchor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a name=\"blub\"></a>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorHref("http://www.kde.org/");
cursor.insertText("Blah", fmt);
QTest::newRow("href anchor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/\">Blah</a></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorHref("http://www.kde.org/?a=1&b=2");
cursor.insertText("Blah", fmt);
QTest::newRow("href anchor with &") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/?a=1&amp;b=2\">Blah</a></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorHref("http://www.kde.org/?a='&b=\"");
cursor.insertText("Blah", fmt);
QTest::newRow("href anchor with ' and \"") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/?a='&amp;b=&quot;\">Blah</a></p>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertTable(2, 2);
QTest::newRow("simpletable") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(1, 4);
table->mergeCells(0, 0, 1, 2);
table->mergeCells(0, 2, 1, 2);
QTest::newRow("tablespans") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td colspan=\"2\"></td>\n<td colspan=\"2\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setBorder(1);
fmt.setCellSpacing(3);
fmt.setCellPadding(3);
fmt.setBackground(QColor("#ff00ff"));
fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50));
fmt.setAlignment(Qt::AlignHCenter);
fmt.setPosition(QTextFrameFormat::FloatRight);
cursor.insertTable(2, 2, fmt);
QTest::newRow("tableattrs") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" style=\" float: right;\" align=\"center\" width=\"50%\" cellspacing=\"3\" cellpadding=\"3\" bgcolor=\"#ff00ff\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setBorder(1);
fmt.setCellSpacing(3);
fmt.setCellPadding(3);
fmt.setBackground(QColor("#ff00ff"));
fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50));
fmt.setAlignment(Qt::AlignHCenter);
fmt.setPosition(QTextFrameFormat::FloatRight);
fmt.setLeftMargin(25);
fmt.setBottomMargin(35);
cursor.insertTable(2, 2, fmt);
QTest::newRow("tableattrs2") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" style=\" float: right; margin-top:0px; margin-bottom:35px; margin-left:25px; margin-right:0px;\" align=\"center\" width=\"50%\" cellspacing=\"3\" cellpadding=\"3\" bgcolor=\"#ff00ff\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setHeaderRowCount(2);
cursor.insertTable(4, 2, fmt);
QTest::newRow("tableheader") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"<thead>\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr></thead>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
QTextTable *subTable = table->cellAt(0, 1).firstCursorPosition().insertTable(1, 1);
subTable->cellAt(0, 0).firstCursorPosition().insertText("Hey");
QTest::newRow("nestedtable") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td>\n<table border=\"1\" cellspacing=\"2\">\n<tr>\n<td>\n<p DEFAULTBLOCKSTYLE>Hey</p></td></tr></table></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
QList<QTextLength> widths;
widths.append(QTextLength());
widths.append(QTextLength(QTextLength::PercentageLength, 30));
widths.append(QTextLength(QTextLength::FixedLength, 40));
fmt.setColumnWidthConstraints(widths);
cursor.insertTable(1, 3, fmt);
QTest::newRow("colwidths") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td width=\"30%\"></td>\n<td width=\"40\"></td></tr>"
"</table>");
}
// ### rowspan/colspan tests, once texttable api for that is back again
//
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(1, 1);
QTextCursor cellCurs = table->cellAt(0, 0).firstCursorPosition();
QTextCharFormat fmt;
fmt.setBackground(QColor("#ffffff"));
cellCurs.mergeBlockCharFormat(fmt);
QTest::newRow("cellproperties") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td bgcolor=\"#ffffff\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
// ### fixme: use programmatic api as soon as we can create floats through it
const char html[] = "<html><body>Blah<img src=\"image.png\" width=\"10\" height=\"20\" style=\"float: right;\" />Blubb</body></html>";
QTest::newRow("image") << QTextDocumentFragment::fromHtml(QString::fromLatin1(html))
<< QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah<img src=\"image.png\" width=\"10\" height=\"20\" style=\"float: right;\" />Blubb</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextImageFormat fmt;
fmt.setName("foo");
fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle);
cursor.insertImage(fmt);
QTest::newRow("image-align-middle") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" style=\"vertical-align: middle;\" /></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextImageFormat fmt;
fmt.setName("foo");
fmt.setVerticalAlignment(QTextCharFormat::AlignTop);
cursor.insertImage(fmt);
QTest::newRow("image-align-top") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" style=\"vertical-align: top;\" /></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextImageFormat fmt;
fmt.setName("foo");
cursor.insertImage(fmt);
cursor.insertImage(fmt);
QTest::newRow("2images") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" /><img src=\"foo\" /></p>");
}
{
CREATE_DOC_AND_CURSOR();
QString txt = QLatin1String("Blah");
txt += QChar::LineSeparator;
txt += QLatin1String("Bar");
cursor.insertText(txt);
QTest::newRow("linebreaks") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE>Blah<br />Bar</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setTopMargin(10);
fmt.setBottomMargin(20);
fmt.setLeftMargin(30);
fmt.setRightMargin(40);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockmargins") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p style=\" margin-top:10px; margin-bottom:20px; margin-left:30px; margin-right:40px; -qt-block-indent:0; text-indent:0px;\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextList *list = cursor.insertList(QTextListFormat::ListDisc);
cursor.insertText("Blubb");
cursor.insertBlock();
cursor.insertText("Blah");
QCOMPARE(list->count(), 2);
QTest::newRow("lists") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li DEFAULTBLOCKSTYLE>Blubb</li>\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
QTextList *list = cursor.insertList(QTextListFormat::ListDisc);
cursor.insertText("Blubb");
cursor.insertBlock();
QTextCharFormat blockCharFmt;
blockCharFmt.setForeground(QColor("#0000ff"));
cursor.mergeBlockCharFormat(blockCharFmt);
QTextCharFormat fmt;
fmt.setForeground(QColor("#ff0000"));
cursor.insertText("Blah", fmt);
QCOMPARE(list->count(), 2);
QTest::newRow("charfmt-for-list-item") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li DEFAULTBLOCKSTYLE>Blubb</li>\n<li style=\" color:#0000ff;\" DEFAULTBLOCKSTYLE><span style=\" color:#ff0000;\">Blah</span></li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setIndent(3);
fmt.setTextIndent(30);
cursor.insertBlock(fmt);
cursor.insertText("Test");
QTest::newRow("block-indent") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:3; text-indent:30px;\">Test</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListDisc);
fmt.setIndent(4);
cursor.insertList(fmt);
cursor.insertText("Blah");
QTest::newRow("list-indent") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 4;\">\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertBlock();
QTest::newRow("emptyblock") << QTextDocumentFragment(&doc)
// after insertBlock() we /do/ have two blocks in the document, so also expect
// these in the html output
<< QString("EMPTYBLOCK") + QString("EMPTYBLOCK");
}
{
CREATE_DOC_AND_CURSOR();
// if you press enter twice in an empty textedit and then insert 'Test'
// you actually get three visible paragraphs, two empty leading ones and
// a third with the actual text. the corresponding html representation
// therefore should also contain three paragraphs.
cursor.insertBlock();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
fmt.setProperty(QTextFormat::FontSizeIncrement, 1);
cursor.mergeBlockCharFormat(fmt);
fmt.setProperty(QTextFormat::FontSizeIncrement, 2);
cursor.insertText("Test", fmt);
QTest::newRow("blockcharfmt") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:x-large; color:#00ff00;\">Test</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
cursor.setBlockCharFormat(fmt);
fmt.setForeground(QColor("#0000ff"));
cursor.insertText("Test", fmt);
QTest::newRow("blockcharfmt2") << QTextDocumentFragment(&doc)
<< QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" color:#0000ff;\">Test</span></p>");
}
{
QTest::newRow("horizontal-ruler") << QTextDocumentFragment::fromHtml("<hr />")
<<
QString("EMPTYBLOCK") +
QString("<hr />");
}
{
QTest::newRow("horizontal-ruler-with-width") << QTextDocumentFragment::fromHtml("<hr width=\"50%\"/>")
<<
QString("EMPTYBLOCK") +
QString("<hr width=\"50%\" />");
}
{
QTest::newRow("horizontal-ruler-with-color") << QTextDocumentFragment::fromHtml("<hr style=\"background-color:green;\"/>")
<<
QString("EMPTYBLOCK") +
QString("<hr style=\"background-color:#008000;\"/>");
}
{
QTest::newRow("horizontal-ruler-with-width-and-color") << QTextDocumentFragment::fromHtml("<hr width=\"50%\" style=\"background-color:green;\"/>")
<<
QString("EMPTYBLOCK") +
QString("<hr width=\"50%\" style=\"background-color:#008000;\"/>");
}
{
CREATE_DOC_AND_CURSOR();
QTextFrame *mainFrame = cursor.currentFrame();
QTextFrameFormat ffmt;
ffmt.setBorder(1);
ffmt.setPosition(QTextFrameFormat::FloatRight);
ffmt.setMargin(2);
ffmt.setWidth(100);
ffmt.setHeight(50);
ffmt.setBackground(QColor("#00ff00"));
cursor.insertFrame(ffmt);
cursor.insertText("Hello World");
cursor = mainFrame->lastCursorPosition();
QTest::newRow("frame") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" style=\"-qt-table-type: frame; float: right; margin-top:2px; margin-bottom:2px; margin-left:2px; margin-right:2px;\" width=\"100\" height=\"50\" bgcolor=\"#00ff00\">\n<tr>\n<td style=\"border: none;\">\n<p DEFAULTBLOCKSTYLE>Hello World</p></td></tr></table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
// fmt.setBackground(QColor("#0000ff"));
cursor.setBlockCharFormat(fmt);
fmt.setForeground(QBrush());
// fmt.setBackground(QBrush());
cursor.insertText("Test", fmt);
// QTest::newRow("nostylebrush") << QTextDocumentFragment(&doc) << QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; color:#00ff00; -qt-blockcharfmt-background-color:#0000ff;\">Test</p>");
QTest::newRow("nostylebrush") << QTextDocumentFragment(&doc) << QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Test</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
table->mergeCells(0, 0, 1, 2);
QTextTableFormat fmt = table->format();
QList<QTextLength> widths;
widths.append(QTextLength(QTextLength::FixedLength, 20));
widths.append(QTextLength(QTextLength::FixedLength, 40));
fmt.setColumnWidthConstraints(widths);
table->setFormat(fmt);
QTest::newRow("mergedtablecolwidths") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td colspan=\"2\"></td></tr>"
"\n<tr>\n<td width=\"20\"></td>\n<td width=\"40\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
cursor.insertText("Blah\nGreen yellow green");
cursor.setPosition(0);
cursor.setPosition(23, QTextCursor::KeepAnchor);
fmt.setBackground(Qt::green);
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
cursor.setPosition(11);
cursor.setPosition(17, QTextCursor::KeepAnchor);
fmt.setBackground(Qt::yellow);
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
QTest::newRow("multiparagraph-bgcolor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Blah</span></p>\n"
"<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Green </span>"
"<span style=\" background-color:#ffff00;\">yellow</span>"
"<span style=\" background-color:#00ff00;\"> green</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor("#0000ff"));
cursor.insertBlock(fmt);
QTextCharFormat charfmt;
charfmt.setBackground(QColor("#0000ff"));
cursor.insertText("Blah", charfmt);
QTest::newRow("nospan-bgcolor") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:#0000ff;\"><span style=\" background-color:#0000ff;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
QTextCharFormat fmt = table->cellAt(0, 0).format();
fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle);
table->cellAt(0, 0).setFormat(fmt);
fmt = table->cellAt(0, 1).format();
fmt.setVerticalAlignment(QTextCharFormat::AlignTop);
table->cellAt(0, 1).setFormat(fmt);
fmt = table->cellAt(1, 0).format();
fmt.setVerticalAlignment(QTextCharFormat::AlignBottom);
table->cellAt(1, 0).setFormat(fmt);
table->cellAt(0, 0).firstCursorPosition().insertText("Blah");
QTest::newRow("table-vertical-alignment") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td style=\" vertical-align:middle;\">\n"
"<p DEFAULTBLOCKSTYLE>Blah</p></td>"
"\n<td style=\" vertical-align:top;\"></td></tr>"
"\n<tr>\n<td style=\" vertical-align:bottom;\"></td>"
"\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
QTextTableCellFormat fmt = table->cellAt(0, 0).format().toTableCellFormat();
fmt.setLeftPadding(1);
table->cellAt(0, 0).setFormat(fmt);
fmt = table->cellAt(0, 1).format().toTableCellFormat();
fmt.setRightPadding(1);
table->cellAt(0, 1).setFormat(fmt);
fmt = table->cellAt(1, 0).format().toTableCellFormat();
fmt.setTopPadding(1);
table->cellAt(1, 0).setFormat(fmt);
fmt = table->cellAt(1, 1).format().toTableCellFormat();
fmt.setBottomPadding(1);
table->cellAt(1, 1).setFormat(fmt);
table->cellAt(0, 0).firstCursorPosition().insertText("Blah");
QTest::newRow("table-cell-paddings") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td style=\" padding-left:1;\">\n"
"<p DEFAULTBLOCKSTYLE>Blah</p></td>"
"\n<td style=\" padding-right:1;\"></td></tr>"
"\n<tr>\n<td style=\" padding-top:1;\"></td>"
"\n<td style=\" padding-bottom:1;\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setBorderBrush(QColor("#0000ff"));
fmt.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
cursor.insertTable(2, 2, fmt);
QTest::newRow("tableborder") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" style=\" border-color:#0000ff; border-style:solid;\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertBlock();
cursor.insertText("Foo");
cursor.block().setUserState(42);
QTest::newRow("userstate") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE -qt-user-state:42;\">Foo</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat blockFmt;
blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
cursor.insertBlock(blockFmt);
cursor.insertText("Foo");
blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore | QTextFormat::PageBreak_AlwaysAfter);
cursor.insertBlock(blockFmt);
cursor.insertText("Bar");
QTextTableFormat tableFmt;
tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
cursor.insertTable(1, 1, tableFmt);
QTest::newRow("pagebreak") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE page-break-before:always;\">Foo</p>"
"\n<p OPENDEFAULTBLOCKSTYLE page-break-before:always; page-break-after:always;\">Bar</p>"
"\n<table border=\"1\" style=\" page-break-after:always;\" cellspacing=\"2\">\n<tr>\n<td></td></tr></table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextListFormat listFmt;
listFmt.setStyle(QTextListFormat::ListDisc);
cursor.insertList(listFmt);
cursor.insertText("Blah");
QTest::newRow("list-ul-margin") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
const QString listHtml = "<ul><li>item-1</li><li>item-2<ul><li>item-2.1</li><li>item-2.2"
"<ul><li>item-2.2.1</li></ul></li><li>item-2.3<ul><li>item-2.3.1"
"</li></ul></li></ul></li><li>item-3</li></ul>";
cursor.insertHtml(listHtml);
QTest::newRow("nested-lists-one") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1</li>\n<li DEFAULTBLOCKSTYLE>item-2\n<ul DEFAULTULSTYLE 2;\">\n<li "
"DEFAULTBLOCKSTYLE>item-2.1</li>\n<li DEFAULTBLOCKSTYLE>item-2.2\n<ul "
"DEFAULTULSTYLE 3;\">\n<li DEFAULTBLOCKSTYLE>item-2.2.1</li></ul></li>\n"
"<li DEFAULTBLOCKSTYLE>item-2.3\n<ul DEFAULTULSTYLE 3;\">\n<li DEFAULTBLOCKSTYLE>"
"item-2.3.1</li></ul></li></ul></li>\n<li DEFAULTLASTLISTYLE>item-3</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
const QString listHtml = "<ul><li>item-1</li><li>item-2<ul><li>item-2.1</li></ul></li></ul>";
cursor.insertHtml(listHtml);
QTest::newRow("nested-lists-two") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\">\n<li "
"DEFAULTBLOCKSTYLE>item-2.1</li></ul></li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
const QString listHtml = "<ul><li>item-1</li><li>item-2<ul><li>item-2.1</li><li>item-2.2"
"</li></ul></li></ul>";
cursor.insertHtml(listHtml);
QTest::newRow("nested-lists-three") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\">\n<li "
"DEFAULTBLOCKSTYLE>item-2.1</li>\n<li DEFAULTBLOCKSTYLE>item-2.2</li></ul>"
"</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
const QString listHtml = "<ul><li>item-1.1</li><li>item-1.2<li></ul>"
"<ul><li>item-2.1</li></ul>";
cursor.insertHtml(listHtml);
QTest::newRow("not-nested-list") << QTextDocumentFragment(&doc)
<< QString("<ul DEFAULTULSTYLE 1;\">\n<li style=\" margin-top:12px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"item-1.1</li>\n<li DEFAULTBLOCKSTYLE>item-1.2</li></ul>\n<ul DEFAULTULSTYLE 1;\">\n"
"<li style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; "
"margin-right:0px; -qt-block-indent:0; text-indent:0px;\">item-2.1</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
const QString listHtml = "<ul><li>bullet</li><li class=\"unchecked\">unchecked item</li><li class=\"checked\">checked item</li></ul>";
cursor.insertHtml(listHtml);
QTest::newRow("list with and without checkboxes") << QTextDocumentFragment(&doc)
<< QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n"
"<li style=\" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">bullet</li>\n"
"<li class=\"unchecked\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">unchecked item</li>\n"
"<li class=\"checked\" style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">checked item</li></ul>");
}
}
void tst_QTextDocument::toHtml()
{
QFETCH(QTextDocumentFragment, input);
QFETCH(QString, expectedOutput);
cursor.insertFragment(input);
expectedOutput.prepend(htmlHead);
expectedOutput.replace("OPENDEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;");
expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
expectedOutput.replace("EMPTYBLOCK", "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n");
expectedOutput.replace("DEFAULTULSTYLE", "style=\"margin-top: 0px; margin-bottom: 0px; "
"margin-left: 0px; margin-right: 0px; -qt-list-indent:");
expectedOutput.replace("DEFAULTLASTLISTYLE", "style=\" margin-top:0px; margin-bottom:12px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
if (expectedOutput.endsWith(QLatin1Char('\n')))
expectedOutput.chop(1);
expectedOutput.append(htmlTail);
QString output = doc->toHtml();
writeActualAndExpected(QTest::currentDataTag(), output, expectedOutput);
QCOMPARE(output, expectedOutput);
QDomDocument document;
QEXPECT_FAIL("charfmt-for-list-item",
"The attribute \"style\" is redefined in the generated HTML, which is not valid "
"according to XML standard. The new QDomDocument implementation follows the XML "
"standard.", Continue);
QVERIFY2(document.setContent(output), "Output was not valid XML");
}
void tst_QTextDocument::toHtml2()
{
QTextDocument doc;
doc.setHtml("<p>text <img src=\"\"> text</p>"); // 4 spaces before the second 'text'
QTextBlock block = doc.firstBlock();
QTextBlock::Iterator iter = block.begin();
QTextFragment f = iter.fragment();
QVERIFY(f.isValid());
QCOMPARE(f.position(), 0);
QCOMPARE(f.length(), 5);
//qDebug() << block.text().mid(f.position(), f.length());
iter++;
f = iter.fragment();
QVERIFY(f.isValid());
QCOMPARE(f.position(), 5);
QCOMPARE(f.length(), 1);
//qDebug() << block.text().mid(f.position(), f.length());
iter++;
f = iter.fragment();
//qDebug() << block.text().mid(f.position(), f.length());
QVERIFY(f.isValid());
QCOMPARE(f.position(), 6);
QCOMPARE(f.length(), 5); // 1 space should be preserved.
QCOMPARE(block.text().mid(f.position(), f.length()), QString(" text"));
doc.setHtml("<table><tr><td> foo</td></tr></table> text"); // 4 spaces before the second 'text'
block = doc.firstBlock().next();
//qDebug() << block.text();
QCOMPARE(block.text(), QString("foo"));
block = block.next();
//qDebug() << block.text();
QCOMPARE(block.text(), QString("text"));
}
void tst_QTextDocument::setFragmentMarkersInHtmlExport()
{
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Leadin");
const int startPos = cursor.position();
cursor.insertText("Test");
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
cursor.insertText("Blah", fmt);
const int endPos = cursor.position();
cursor.insertText("Leadout", QTextCharFormat());
cursor.setPosition(startPos);
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
QTextDocumentFragment fragment(cursor);
QString expected = htmlHead;
expected.replace(QRegularExpression("<body.*>"), QString("<body>"));
expected += QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><!--StartFragment-->Test<span style=\" color:#00ff00;\">Blah</span><!--EndFragment--></p>") + htmlTail;
QCOMPARE(fragment.toHtml(), expected);
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Leadin");
const int startPos = cursor.position();
cursor.insertText("Test");
const int endPos = cursor.position();
cursor.insertText("Leadout", QTextCharFormat());
cursor.setPosition(startPos);
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
QTextDocumentFragment fragment(cursor);
QString expected = htmlHead;
expected.replace(QRegularExpression("<body.*>"), QString("<body>"));
expected += QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><!--StartFragment-->Test<!--EndFragment--></p>") + htmlTail;
QCOMPARE(fragment.toHtml(), expected);
}
}
void tst_QTextDocument::setMediaRule()
{
{
CREATE_DOC_AND_CURSOR();
doc.setDefaultStyleSheet("@media screen { p { background:#000000 } } @media print { p { background:#ffffff } }");
doc.setHtml("<p>Hello World</p>");
QString expected = htmlHead;
expected += QString("<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#000000;\"><span style=\" background-color:#000000;\">Hello World</span></p>") + htmlTail;
QCOMPARE(doc.toHtml(), expected);
}
{
CREATE_DOC_AND_CURSOR();
doc.setDefaultStyleSheet("@media screen { p { background:#000000 } } @media print { p { background:#ffffff } }");
doc.setMetaInformation(QTextDocument::CssMedia, "screen");
doc.setHtml("<p>Hello World</p>");
QString expected = htmlHead;
expected += QString("<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#000000;\"><span style=\" background-color:#000000;\">Hello World</span></p>") + htmlTail;
QCOMPARE(doc.toHtml(), expected);
}
{
CREATE_DOC_AND_CURSOR();
doc.setDefaultStyleSheet("@media screen { p { background:#000000 } } @media print { p { background:#ffffff } }");
doc.setMetaInformation(QTextDocument::CssMedia, "print");
doc.setHtml("<p>Hello World</p>");
QString expected = htmlHead;
expected += QString("<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;\"><span style=\" background-color:#ffffff;\">Hello World</span></p>") + htmlTail;
QCOMPARE(doc.toHtml(), expected);
}
}
void tst_QTextDocument::toHtmlBodyBgColor()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setBackground(QColor("#0000ff"));
doc.rootFrame()->setFrameFormat(fmt);
QString expectedHtml = htmlHead;
expectedHtml.insert(htmlHead.size() - 2, " bgcolor=\"#0000ff\"");
expectedHtml += "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
+ htmlTail;
writeActualAndExpected(QTest::currentDataTag(), doc.toHtml(), expectedHtml);
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::toHtmlBodyBgColorRgba()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setBackground(QColor(255, 0, 0, 51));
doc.rootFrame()->setFrameFormat(fmt);
QString expectedHtml = htmlHead;
expectedHtml.insert(htmlHead.size() - 2, " bgcolor=\"rgba(255,0,0,0.2)\"");
expectedHtml += "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
+ htmlTail;
writeActualAndExpected(QTest::currentDataTag(), doc.toHtml(), expectedHtml);
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::toHtmlBodyBgColorTransparent()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setBackground(QColor(255, 0, 0, 0));
doc.rootFrame()->setFrameFormat(fmt);
QString expectedHtml = htmlHead;
expectedHtml.insert(htmlHead.size() - 2, " bgcolor=\"transparent\"");
expectedHtml += "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
+ htmlTail;
writeActualAndExpected(QTest::currentDataTag(), doc.toHtml(), expectedHtml);
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::toHtmlRootFrameProperties()
{
CREATE_DOC_AND_CURSOR();
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setTopMargin(10);
fmt.setLeftMargin(10);
fmt.setBorder(2);
doc.rootFrame()->setFrameFormat(fmt);
cursor.insertText("Blah");
QString expectedOutput("<table border=\"2\" style=\"-qt-table-type: root; margin-top:10px; "
"margin-bottom:4px; margin-left:10px; margin-right:4px;\">\n"
"<tr>\n<td style=\"border: none;\">\n"
"<p DEFAULTBLOCKSTYLE>Blah</p></td></tr></table>");
expectedOutput.prepend(htmlHead);
expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
expectedOutput.append(htmlTail);
writeActualAndExpected(QTest::currentTestFunction(), doc.toHtml(), expectedOutput);
QCOMPARE(doc.toHtml(), expectedOutput);
}
void tst_QTextDocument::toHtmlLineHeightProperties()
{
CREATE_DOC_AND_CURSOR();
QTextBlock block = doc.firstBlock();
QTextBlockFormat blockFormat = block.blockFormat();
blockFormat.setLineHeight(200, QTextBlockFormat::ProportionalHeight);
cursor.setBlockFormat(blockFormat);
cursor.insertText("Blah");
QString expectedOutput("<p DEFAULTBLOCKSTYLE line-height:200%;\">Blah</p>");
expectedOutput.prepend(htmlHead);
expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;");
expectedOutput.append(htmlTail);
QCOMPARE(doc.toHtml(), expectedOutput);
}
void tst_QTextDocument::toHtmlDefaultFontSpacingProperties()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QFont fnt = doc.defaultFont();
fnt.setLetterSpacing(QFont::AbsoluteSpacing, 13);
fnt.setWordSpacing(15);
doc.setDefaultFont(fnt);
QString expectedOutput = htmlHead;
expectedOutput.insert(htmlHead.size() - 3, " letter-spacing:13px; word-spacing:15px;");
expectedOutput +=
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
+ htmlTail;
writeActualAndExpected(QTest::currentTestFunction(), doc.toHtml(), expectedOutput);
QCOMPARE(doc.toHtml(), expectedOutput);
}
void tst_QTextDocument::toHtmlTextDecorationUnderline()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Some text");
QFont fnt = doc.defaultFont();
fnt.setUnderline(true);
doc.setDefaultFont(fnt);
QString expectedOutput = htmlHead;
expectedOutput.insert(htmlHead.size() - 3, " text-decoration: underline;");
expectedOutput +=
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Some text</p>"
+ htmlTail;
writeActualAndExpected("toHtmlTextDecorationUnderline1", doc.toHtml(), expectedOutput);
QCOMPARE(doc.toHtml(), expectedOutput);
QTextCharFormat format;
format.setFontUnderline(false);
cursor.select(QTextCursor::Document);
cursor.mergeCharFormat(format);
expectedOutput = htmlHead;
expectedOutput.insert(htmlHead.size() - 3, " text-decoration: underline;");
expectedOutput +=
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; "
"margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
"<span style=\" text-decoration:none;\">Some text</span></p>"
+ htmlTail;
writeActualAndExpected("toHtmlTextDecorationUnderline2", doc.toHtml(), expectedOutput);
QCOMPARE(doc.toHtml(), expectedOutput);
}
void tst_QTextDocument::capitalizationHtmlInExport()
{
doc->setPlainText("Test");
QRegularExpression re(".*span style=\"(.*)\">Test.*");
QCOMPARE(re.captureCount(), 1);
QVERIFY(!re.match(doc->toHtml()).hasMatch()); // no span
QTextCursor cursor(doc);
cursor.setPosition(4, QTextCursor::KeepAnchor);
QTextCharFormat cf;
cf.setFontCapitalization(QFont::SmallCaps);
cursor.mergeCharFormat(cf);
const QString smallcaps = doc->toHtml();
auto match = re.match(doc->toHtml());
QVERIFY(match.hasMatch());
QCOMPARE(match.captured(1).trimmed(), QString("font-variant:small-caps;"));
cf.setFontCapitalization(QFont::AllUppercase);
cursor.mergeCharFormat(cf);
const QString uppercase = doc->toHtml();
match = re.match(doc->toHtml());
QVERIFY(match.hasMatch());
QCOMPARE(match.captured(1).trimmed(), QString("text-transform:uppercase;"));
cf.setFontCapitalization(QFont::AllLowercase);
cursor.mergeCharFormat(cf);
const QString lowercase = doc->toHtml();
match = re.match(doc->toHtml());
QVERIFY(match.hasMatch());
QCOMPARE(match.captured(1).trimmed(), QString("text-transform:lowercase;"));
doc->setHtml(smallcaps);
cursor.setPosition(1);
QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::SmallCaps);
doc->setHtml(uppercase);
QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllUppercase);
doc->setHtml(lowercase);
QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllLowercase);
}
void tst_QTextDocument::wordspacingHtmlExport()
{
doc->setPlainText("Test");
QRegularExpression re(".*span style=\"(.*)\">Test.*");
QCOMPARE(re.captureCount(), 1);
QVERIFY(!re.match(doc->toHtml()).hasMatch()); // no span
QTextCursor cursor(doc);
cursor.setPosition(4, QTextCursor::KeepAnchor);
QTextCharFormat cf;
cf.setFontWordSpacing(4);
cursor.mergeCharFormat(cf);
auto match = re.match(doc->toHtml());
QVERIFY(match.hasMatch());
QCOMPARE(match.captured(1).trimmed(), QString("word-spacing:4px;"));
cf.setFontWordSpacing(-8.5);
cursor.mergeCharFormat(cf);
match = re.match(doc->toHtml());
QVERIFY(match.hasMatch());
QCOMPARE(match.captured(1).trimmed(), QString("word-spacing:-8.5px;"));
}
class CursorPosSignalSpy : public QObject
{
Q_OBJECT
public:
CursorPosSignalSpy(QTextDocument *doc)
{
calls = 0;
connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)),
this, SLOT(cursorPositionChanged(QTextCursor)));
}
int calls;
private slots:
void cursorPositionChanged(const QTextCursor &)
{
++calls;
}
};
void tst_QTextDocument::cursorPositionChanged()
{
CursorPosSignalSpy spy(doc);
cursor.insertText("Test");
QCOMPARE(spy.calls, 1);
spy.calls = 0;
QTextCursor unrelatedCursor(doc);
unrelatedCursor.insertText("Blah");
QCOMPARE(spy.calls, 2);
spy.calls = 0;
cursor.insertText("Blah");
QCOMPARE(spy.calls, 1);
spy.calls = 0;
cursor.movePosition(QTextCursor::PreviousCharacter);
QCOMPARE(spy.calls, 0);
}
void tst_QTextDocument::cursorPositionChangedOnSetText()
{
CursorPosSignalSpy spy(doc);
// doc has one QTextCursor stored in the
// cursor member variable, thus the signal
// gets emitted once.
doc->setPlainText("Foo\nBar\nBaz\nBlub\nBlah");
QCOMPARE(spy.calls, 1);
spy.calls = 0;
doc->setHtml("<p>Foo<p>Bar<p>Baz<p>Blah");
QCOMPARE(spy.calls, 1);
}
void tst_QTextDocument::textFrameIterator()
{
cursor.insertTable(1, 1);
int blockCount = 0;
int frameCount = 0;
for (QTextFrame::Iterator frameIt = doc->rootFrame()->begin();
!frameIt.atEnd(); ++frameIt) {
if (frameIt.currentFrame())
++frameCount;
else if (frameIt.currentBlock().isValid())
++blockCount;
}
QEXPECT_FAIL("", "This is currently worked around in the html export but needs fixing!", Continue);
QCOMPARE(blockCount, 0);
QCOMPARE(frameCount, 1);
}
class TestSyntaxHighlighter : public QObject
{
Q_OBJECT
public:
inline TestSyntaxHighlighter(QTextDocument *doc) : QObject(doc), ok(false) {}
bool ok;
private slots:
inline void markBlockDirty(int from, int charsRemoved, int charsAdded)
{
Q_UNUSED(charsRemoved);
Q_UNUSED(charsAdded);
QTextDocument *doc = static_cast<QTextDocument *>(parent());
QTextBlock block = doc->findBlock(from);
QTestDocumentLayout *lout = qobject_cast<QTestDocumentLayout *>(doc->documentLayout());
lout->called = false;
doc->markContentsDirty(block.position(), block.length());
ok = (lout->called == false);
}
inline void modifyBlockAgain(int from, int charsRemoved, int charsAdded)
{
Q_UNUSED(charsRemoved);
Q_UNUSED(charsAdded);
QTextDocument *doc = static_cast<QTextDocument *>(parent());
QTextBlock block = doc->findBlock(from);
QTextCursor cursor(block);
QTestDocumentLayout *lout = qobject_cast<QTestDocumentLayout *>(doc->documentLayout());
lout->called = false;
cursor.insertText("Foo");
ok = (lout->called == true);
}
};
void tst_QTextDocument::markContentsDirty()
{
QTestDocumentLayout *lout = new QTestDocumentLayout(doc);
doc->setDocumentLayout(lout);
TestSyntaxHighlighter *highlighter = new TestSyntaxHighlighter(doc);
connect(doc, SIGNAL(contentsChange(int,int,int)),
highlighter, SLOT(markBlockDirty(int,int,int)));
highlighter->ok = false;
cursor.insertText("Some dummy text blah blah");
QVERIFY(highlighter->ok);
disconnect(doc, SIGNAL(contentsChange(int,int,int)),
highlighter, SLOT(markBlockDirty(int,int,int)));
connect(doc, SIGNAL(contentsChange(int,int,int)),
highlighter, SLOT(modifyBlockAgain(int,int,int)));
highlighter->ok = false;
cursor.insertText("FooBar");
QVERIFY(highlighter->ok);
lout->called = false;
doc->markContentsDirty(1, 4);
QVERIFY(lout->called);
}
void tst_QTextDocument::clonePreservesMetaInformation()
{
const QString title("Foobar");
const QString url("about:blank");
const QString media("print");
doc->setHtml("<html><head><title>" + title + "</title></head><body>Hrm</body></html>");
doc->setMetaInformation(QTextDocument::DocumentUrl, url);
doc->setMetaInformation(QTextDocument::CssMedia, media);
QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), title);
QCOMPARE(doc->metaInformation(QTextDocument::DocumentUrl), url);
QCOMPARE(doc->metaInformation(QTextDocument::CssMedia), media);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->metaInformation(QTextDocument::DocumentTitle), title);
QCOMPARE(clone->metaInformation(QTextDocument::DocumentUrl), url);
QCOMPARE(clone->metaInformation(QTextDocument::CssMedia), media);
delete clone;
}
void tst_QTextDocument::clonePreservesPageSize()
{
QSizeF sz(100., 100.);
doc->setPageSize(sz);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->pageSize(), sz);
delete clone;
}
void tst_QTextDocument::clonePreservesPageBreakPolicies()
{
QTextTableFormat tableFmt;
tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
QTextBlockFormat blockFmt;
blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
QTextCursor cursor(doc);
cursor.setBlockFormat(blockFmt);
cursor.insertText("foo");
cursor.insertTable(2, 2, tableFmt);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->begin().blockFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysBefore);
QVERIFY(!clone->rootFrame()->childFrames().isEmpty());
QCOMPARE(clone->rootFrame()->childFrames().first()->frameFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysAfter);
delete clone;
}
void tst_QTextDocument::clonePreservesDefaultFont()
{
QFont f = doc->defaultFont();
QVERIFY(f.pointSize() != 100);
f.setPointSize(100);
doc->setDefaultFont(f);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->defaultFont(), f);
delete clone;
}
void tst_QTextDocument::clonePreservesResources()
{
QUrl testUrl(":/foobar");
QVariant testResource("hello world");
doc->addResource(QTextDocument::ImageResource, testUrl, testResource);
QTextDocument *clone = doc->clone();
QVERIFY(clone->resource(QTextDocument::ImageResource, testUrl) == testResource);
delete clone;
}
void tst_QTextDocument::clonePreservesUserStates()
{
QTextCursor cursor(doc);
cursor.insertText("bla bla bla");
cursor.block().setUserState(1);
cursor.insertBlock();
cursor.insertText("foo bar");
cursor.block().setUserState(2);
cursor.insertBlock();
cursor.insertText("no user state");
QTextDocument *clone = doc->clone();
QTextBlock b1 = doc->begin(), b2 = clone->begin();
while (b1 != doc->end()) {
b1 = b1.next();
b2 = b2.next();
QCOMPARE(b1.userState(), b2.userState());
}
QCOMPARE(b2, clone->end());
delete clone;
}
void tst_QTextDocument::clonePreservesRootFrameFormat()
{
doc->setPlainText("Hello");
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
fmt.setMargin(200);
doc->rootFrame()->setFrameFormat(fmt);
QCOMPARE(doc->rootFrame()->frameFormat().margin(), qreal(200));
QTextDocument *copy = doc->clone();
QCOMPARE(copy->rootFrame()->frameFormat().margin(), qreal(200));
delete copy;
}
void tst_QTextDocument::clonePreservesIndentWidth()
{
doc->setIndentWidth(42);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->indentWidth(), qreal(42));
delete clone;
}
void tst_QTextDocument::clonePreservesFormatsWhenEmpty()
{
QTextDocument document;
QTextCursor cursor(&document);
// Change a few char format attributes
QTextCharFormat charFormat;
charFormat.setFontPointSize(charFormat.fontPointSize() + 1);
charFormat.setFontWeight(charFormat.fontWeight() + 1);
cursor.setBlockCharFormat(charFormat);
// Change a few block format attributes
QTextBlockFormat blockFormat;
blockFormat.setAlignment(Qt::AlignRight); // The default is Qt::AlignLeft
blockFormat.setIndent(blockFormat.indent() + 1);
cursor.setBlockFormat(blockFormat);
auto clone = document.clone();
QTextCursor cloneCursor(clone);
QCOMPARE(cloneCursor.blockCharFormat().fontPointSize(), charFormat.fontPointSize());
QCOMPARE(cloneCursor.blockCharFormat().fontWeight(), charFormat.fontWeight());
QCOMPARE(cloneCursor.blockFormat().alignment(), blockFormat.alignment());
QCOMPARE(cloneCursor.blockFormat().indent(), blockFormat.indent());
delete clone;
}
void tst_QTextDocument::blockCount()
{
QCOMPARE(doc->blockCount(), 1);
cursor.insertBlock();
QCOMPARE(doc->blockCount(), 2);
cursor.insertBlock();
QCOMPARE(doc->blockCount(), 3);
cursor.insertText("blah blah");
QCOMPARE(doc->blockCount(), 3);
doc->undo();
doc->undo();
QCOMPARE(doc->blockCount(), 2);
doc->undo();
QCOMPARE(doc->blockCount(), 1);
}
void tst_QTextDocument::resolvedFontInEmptyFormat()
{
QFont font;
font.setPointSize(42);
doc->setDefaultFont(font);
QTextCharFormat fmt = doc->begin().charFormat();
QVERIFY(fmt.properties().isEmpty());
QCOMPARE(fmt.font(), font);
}
void tst_QTextDocument::defaultRootFrameMargin()
{
QCOMPARE(doc->rootFrame()->frameFormat().margin(), 4.0);
}
class TestDocument : public QTextDocument
{
public:
inline TestDocument(const QUrl &testUrl, const QString &testString)
: url(testUrl), string(testString), resourceLoaded(false) {}
bool hasResourceCached();
protected:
virtual QVariant loadResource(int type, const QUrl &name) override;
private:
QUrl url;
QString string;
bool resourceLoaded;
};
bool TestDocument::hasResourceCached()
{
resourceLoaded = false;
resource(QTextDocument::ImageResource, url);
return !resourceLoaded;
}
QVariant TestDocument::loadResource(int type, const QUrl &name)
{
if (type == QTextDocument::ImageResource
&& name == url) {
resourceLoaded = true;
return string;
}
return QTextDocument::loadResource(type, name);
}
void tst_QTextDocument::clearResources()
{
// regular resource for QTextDocument
QUrl testUrl(":/foobar");
QVariant testResource("hello world");
// implicitly cached resource, initially loaded through TestDocument::loadResource()
QUrl cacheUrl(":/blub");
QString cacheResource("mah");
TestDocument doc(cacheUrl, cacheResource);
doc.addResource(QTextDocument::ImageResource, testUrl, testResource);
QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
doc.setPlainText("Hah");
QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
doc.setHtml("<b>Mooo</b><img src=\":/blub\"/>");
QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource);
doc.clear();
QVERIFY(!doc.resource(QTextDocument::ImageResource, testUrl).isValid());
QVERIFY(!doc.hasResourceCached());
doc.clear();
doc.setHtml("<b>Mooo</b><img src=\":/blub\"/>");
QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource);
doc.setPlainText("Foob");
QVERIFY(!doc.hasResourceCached());
}
void tst_QTextDocument::setPlainText()
{
doc->setPlainText("Hello World");
QString s("");
doc->setPlainText(s);
QCOMPARE(doc->toPlainText(), s);
}
void tst_QTextDocument::toPlainText_data()
{
QTest::addColumn<QString>("html");
QTest::addColumn<QString>("expectedPlainText");
QTest::newRow("nbsp") << "Hello&nbsp;World" << "Hello World";
QTest::newRow("empty_div") << "<div></div>hello" << "hello";
QTest::newRow("br_and_p") << "<p>first<br></p><p>second<br></p>" << "first\n\nsecond\n";
QTest::newRow("div") << "first<div>second<br>third</div>fourth" << "first\nsecond\nthird\nfourth"; // <div> and </div> become newlines...
QTest::newRow("br_text_end_of_div") << "<div><div>first<br>moretext</div>second<br></div>" << "first\nmoretext\nsecond\n"; // ... when there is text before <div>
QTest::newRow("br_end_of_div_like_gmail") << "<div><div><div>first<br></div>second<br></div>third<br></div>" << "first\nsecond\nthird\n"; // ... and when there is text before </div>
QTest::newRow("p_and_div") << "<div><div>first<p>second</p></div>third</div>" << "first\nsecond\nthird";
}
void tst_QTextDocument::toPlainText()
{
QFETCH(QString, html);
QFETCH(QString, expectedPlainText);
doc->setHtml(html);
QCOMPARE(doc->toPlainText(), expectedPlainText);
}
void tst_QTextDocument::toRawText()
{
doc->setHtml("&nbsp;");
QString rawText = doc->toRawText();
QCOMPARE(rawText.size(), 1);
QCOMPARE(rawText.at(0).unicode(), ushort(QChar::Nbsp));
}
void tst_QTextDocument::deleteTextObjectsOnClear()
{
QPointer<QTextTable> table = cursor.insertTable(2, 2);
QVERIFY(!table.isNull());
doc->clear();
QVERIFY(table.isNull());
}
void tst_QTextDocument::defaultStyleSheet()
{
const QColor green("green");
const QString sheet("p { background-color: green; }");
QVERIFY(doc->defaultStyleSheet().isEmpty());
doc->setDefaultStyleSheet(sheet);
QCOMPARE(doc->defaultStyleSheet(), sheet);
cursor.insertHtml("<p>test");
QTextBlockFormat fmt = doc->begin().blockFormat();
QCOMPARE(fmt.background().color(), green);
doc->clear();
cursor.insertHtml("<p>test");
fmt = doc->begin().blockFormat();
QCOMPARE(fmt.background().color(), green);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->defaultStyleSheet(), sheet);
cursor = QTextCursor(clone);
cursor.insertHtml("<p>test");
fmt = clone->begin().blockFormat();
QCOMPARE(fmt.background().color(), green);
delete clone;
cursor = QTextCursor(doc);
cursor.insertHtml("<p>test");
fmt = doc->begin().blockFormat();
QCOMPARE(fmt.background().color(), green);
doc->clear();
cursor.insertHtml("<style>p { background-color: red; }</style><p>test");
fmt = doc->begin().blockFormat();
QCOMPARE(fmt.background().color(), QColor(Qt::red));
doc->clear();
doc->setDefaultStyleSheet("invalid style sheet....");
cursor.insertHtml("<p>test");
fmt = doc->begin().blockFormat();
QVERIFY(fmt.background().color() != QColor("green"));
}
void tst_QTextDocument::defaultTableStyle_data()
{
QTest::addColumn<QString>("css");
QTest::addColumn<QString>("html");
QTest::addColumn<QList<QBrush>>("borderBrushes");
const QString htmlHeader(R"(
<tr>
<th>1</th> <th>2</th>
</tr>
)");
const QString htmlCells(R"(
<tr>
<td>A</td> <td>B</td>
</tr>
)");
const QString cssEachSide = R"({
border-top-color: red;
border-bottom-color: blue;
border-left-color: green;
border-right-color: yellow;
})";
const QString cssOneColor = R"({ border-color: red; })";
const QString cssFourColors = R"({ border-color: red blue green yellow; })";
QTest::addRow("td, each side") << QString("td ") + cssEachSide
<< htmlCells
<< QList<QBrush>{Qt::red, Qt::blue, QColor("green"), Qt::yellow};
QTest::addRow("th, each side") << QString("th ") + cssEachSide
<< htmlHeader
<< QList<QBrush>{Qt::red, Qt::blue, QColor("green"), Qt::yellow};
QTest::addRow("th+td, each side") << QString("th %1 td %1").arg(cssEachSide)
<< htmlHeader + htmlCells
<< QList<QBrush>{Qt::red, Qt::blue, QColor("green"), Qt::yellow};
QTest::addRow("td, one color") << QString("td ") + cssOneColor
<< htmlCells
<< QList<QBrush>{Qt::red, Qt::red, Qt::red, Qt::red};
QTest::addRow("th, one color") << QString("th ") + cssOneColor
<< htmlHeader
<< QList<QBrush>{Qt::red, Qt::red, Qt::red, Qt::red};
QTest::addRow("th+td, one color") << QString("th %1 td %1").arg(cssOneColor)
<< htmlHeader + htmlCells
<< QList<QBrush>{Qt::red, Qt::red, Qt::red, Qt::red};
// css order is top, right, bottom, left
QTest::addRow("td, four colors") << QString("td ") + cssFourColors
<< htmlCells
<< QList<QBrush>{Qt::red, QColor("green"), Qt::yellow, Qt::blue};
}
void tst_QTextDocument::defaultTableStyle()
{
QFETCH(QString, css);
QFETCH(QString, html);
QFETCH(QList<QBrush>, borderBrushes);
CREATE_DOC_AND_CURSOR();
doc.setDefaultStyleSheet(css);
doc.setHtml(QString("<html><body><table>%1</table></body></html>").arg(html));
const QTextFrame *frame = doc.rootFrame();
const QTextTable *table = nullptr;
for (auto it = frame->begin(); !table && !it.atEnd(); ++it)
table = qobject_cast<const QTextTable*>(it.currentFrame());
QVERIFY(table);
const QList<QTextFormat::Property> brushProperties = {
QTextFormat::TableCellTopBorderBrush,
QTextFormat::TableCellBottomBorderBrush,
QTextFormat::TableCellLeftBorderBrush,
QTextFormat::TableCellRightBorderBrush
};
for (int row = 0; row < table->rows(); ++row) {
for (int column = 0; column < table->columns(); ++column) {
auto cellDetails = qScopeGuard([&]{
qWarning("Failure was in cell %d/%d", row, column);
});
const QTextTableCell cell = table->cellAt(row, column);
const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
QList<QBrush> brushes;
for (const auto side : brushProperties) {
QVariant sideProperty = cellFormat.property(side);
QVERIFY(sideProperty.isValid());
QVERIFY(sideProperty.typeId() == qMetaTypeId<QBrush>());
brushes << sideProperty.value<QBrush>();
}
auto errorDetails = qScopeGuard([&]{
if (brushes.size() != borderBrushes.size()) {
qWarning("Different count: %lld vs %lld", brushes.size(), borderBrushes.size());
return;
}
for (int i = 0; i < brushes.size(); ++i) {
QString side;
QDebug(&side) << QTextFormat::Property(QTextFormat::TableCellTopBorderBrush + i);
QString actual;
QDebug(&actual) << brushes.at(i);
QString expected;
QDebug(&expected) << borderBrushes.at(i);
if (expected != actual) {
qWarning("\n %s:\n\tActual: %s\n\tExpected:%s", qPrintable(side),
qPrintable(actual), qPrintable(expected));
}
}
});
QVERIFY2(borderBrushes == brushes, // QCOMPARE doesn't generate helpful output anyway
qPrintable(QString("for cell %1/%2").arg(row).arg(column)));
errorDetails.dismiss();
cellDetails.dismiss();
}
}
}
void tst_QTextDocument::maximumBlockCount()
{
QCOMPARE(doc->maximumBlockCount(), 0);
QVERIFY(doc->isUndoRedoEnabled());
cursor.insertBlock();
cursor.insertText("Blah");
cursor.insertBlock();
cursor.insertText("Foo");
QCOMPARE(doc->blockCount(), 3);
QCOMPARE(doc->toPlainText(), QString("\nBlah\nFoo"));
doc->setMaximumBlockCount(1);
QVERIFY(!doc->isUndoRedoEnabled());
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(doc->toPlainText(), QString("Foo"));
cursor.insertBlock();
cursor.insertText("Hello");
doc->setMaximumBlockCount(1);
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(doc->toPlainText(), QString("Hello"));
doc->setMaximumBlockCount(100);
for (int i = 0; i < 1000; ++i) {
cursor.insertBlock();
cursor.insertText("Blah)");
QVERIFY(doc->blockCount() <= 100);
}
cursor.movePosition(QTextCursor::End);
QCOMPARE(cursor.blockNumber(), 99);
QTextCharFormat fmt;
fmt.setFontItalic(true);
cursor.setBlockCharFormat(fmt);
cursor.movePosition(QTextCursor::Start);
QVERIFY(!cursor.blockCharFormat().fontItalic());
doc->setMaximumBlockCount(1);
QVERIFY(cursor.blockCharFormat().fontItalic());
cursor.insertTable(2, 2);
QCOMPARE(doc->blockCount(), 6);
cursor.insertBlock();
QCOMPARE(doc->blockCount(), 1);
}
void tst_QTextDocument::adjustSize()
{
// avoid ugly tooltips like in task 125583
QString text("Test Text");
doc->setPlainText(text);
doc->rootFrame()->setFrameFormat(QTextFrameFormat());
doc->adjustSize();
QCOMPARE(doc->size().width(), doc->idealWidth());
}
void tst_QTextDocument::initialUserData()
{
doc->setPlainText("Hello");
QTextBlock block = doc->begin();
block.setUserData(new QTextBlockUserData);
QVERIFY(block.userData());
doc->documentLayout();
QVERIFY(block.userData());
doc->setDocumentLayout(new QTestDocumentLayout(doc));
QVERIFY(!block.userData());
}
void tst_QTextDocument::html_defaultFont()
{
QFont f;
f.setItalic(true);
f.setWeight(QFont::Bold);
doc->setDefaultFont(f);
doc->setPlainText("Test");
QString bodyPart = QString::fromLatin1("<body style=\" font-family:'%1'; font-size:%2; "
"font-weight:%3; font-style:italic;\">")
.arg(f.family())
.arg(cssFontSizeString(f))
.arg(f.weight());
QString html = doc->toHtml();
if (!html.contains(bodyPart)) {
qDebug() << "html:" << html;
qDebug() << "expected body:" << bodyPart;
QVERIFY(html.contains(bodyPart));
}
if (html.contains("span"))
qDebug() << "html:" << html;
QVERIFY(!html.contains("<span style"));
}
void tst_QTextDocument::blockCountChanged()
{
QSignalSpy spy(doc, SIGNAL(blockCountChanged(int)));
doc->setPlainText("Foo");
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(spy.size(), 0);
spy.clear();
doc->setPlainText("Foo\nBar");
QCOMPARE(doc->blockCount(), 2);
QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).value(0).toInt(), 2);
spy.clear();
cursor.movePosition(QTextCursor::End);
cursor.insertText("Blahblah");
QCOMPARE(spy.size(), 0);
cursor.insertBlock();
QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).value(0).toInt(), 3);
spy.clear();
doc->undo();
QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).value(0).toInt(), 2);
}
void tst_QTextDocument::nonZeroDocumentLengthOnClear()
{
QTestDocumentLayout *lout = new QTestDocumentLayout(doc);
doc->setDocumentLayout(lout);
doc->clear();
QVERIFY(lout->called);
QVERIFY(!lout->lastDocumentLengths.contains(0));
}
void tst_QTextDocument::setTextPreservesUndoRedoEnabled()
{
QVERIFY(doc->isUndoRedoEnabled());
doc->setPlainText("Test");
QVERIFY(doc->isUndoRedoEnabled());
doc->setUndoRedoEnabled(false);
QVERIFY(!doc->isUndoRedoEnabled());
doc->setPlainText("Test2");
QVERIFY(!doc->isUndoRedoEnabled());
doc->setHtml("<p>hello");
QVERIFY(!doc->isUndoRedoEnabled());
}
void tst_QTextDocument::firstLast()
{
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(doc->firstBlock(), doc->lastBlock());
doc->setPlainText("Hello\nTest\nWorld");
QCOMPARE(doc->blockCount(), 3);
QVERIFY(doc->firstBlock() != doc->lastBlock());
QCOMPARE(doc->firstBlock().text(), QString("Hello"));
QCOMPARE(doc->lastBlock().text(), QString("World"));
// manual forward loop
QTextBlock block = doc->firstBlock();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Hello"));
block = block.next();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Test"));
block = block.next();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("World"));
block = block.next();
QVERIFY(!block.isValid());
// manual backward loop
block = doc->lastBlock();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("World"));
block = block.previous();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Test"));
block = block.previous();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Hello"));
block = block.previous();
QVERIFY(!block.isValid());
}
const QString backgroundImage_html("<body><table><tr><td background=\"foo.png\">Blah</td></tr></table></body>");
void tst_QTextDocument::backgroundImage_checkExpectedHtml(const QTextDocument &doc)
{
QString expectedHtml = htmlHead +
"<table border=\"0\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;\" cellspacing=\"2\" cellpadding=\"0\">"
"\n<tr>\n<td background=\"foo.png\">"
"\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
"</td></tr></table>" + htmlTail;
writeActualAndExpected(QTest::currentTestFunction(), doc.toHtml(), expectedHtml);
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::buildRegExpData()
{
QTest::addColumn<QString>("haystack");
QTest::addColumn<QString>("needle");
QTest::addColumn<int>("flags");
QTest::addColumn<int>("from");
QTest::addColumn<int>("anchor");
QTest::addColumn<int>("position");
// match integers 0 to 99
QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2;
// match ampersands but not &amp;
QTest::newRow("2") << "His &amp; hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16;
//backward search
QTest::newRow("3") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "h" << int(QTextDocument::FindBackward) << 18 << 8 << 9;
}
void tst_QTextDocument::backgroundImage_toHtml()
{
CREATE_DOC_AND_CURSOR();
doc.setHtml(backgroundImage_html);
backgroundImage_checkExpectedHtml(doc);
}
void tst_QTextDocument::backgroundImage_toHtml2()
{
CREATE_DOC_AND_CURSOR();
cursor.insertHtml(backgroundImage_html);
backgroundImage_checkExpectedHtml(doc);
}
void tst_QTextDocument::backgroundImage_clone()
{
CREATE_DOC_AND_CURSOR();
doc.setHtml(backgroundImage_html);
QTextDocument *clone = doc.clone();
backgroundImage_checkExpectedHtml(*clone);
delete clone;
}
void tst_QTextDocument::backgroundImage_copy()
{
CREATE_DOC_AND_CURSOR();
doc.setHtml(backgroundImage_html);
QTextDocumentFragment fragment(&doc);
{
CREATE_DOC_AND_CURSOR();
cursor.insertFragment(fragment);
backgroundImage_checkExpectedHtml(doc);
}
}
void tst_QTextDocument::documentCleanup()
{
QTextDocument doc;
QTextCursor cursor(&doc);
cursor.insertText("d\nfoo\nbar\n");
doc.documentLayout(); // forces relayout
// remove char 1
cursor.setPosition(0);
QSizeF size = doc.documentLayout()->documentSize();
cursor.deleteChar();
// the size should be unchanged.
QCOMPARE(doc.documentLayout()->documentSize(), size);
}
void tst_QTextDocument::characterAt()
{
QTextDocument doc;
QTextCursor cursor(&doc);
QString text("12345\n67890");
cursor.insertText(text);
int length = doc.characterCount();
QCOMPARE(length, text.size() + 1);
QCOMPARE(doc.characterAt(length-1), QChar(QChar::ParagraphSeparator));
QCOMPARE(doc.characterAt(-1), QChar());
QCOMPARE(doc.characterAt(length), QChar());
QCOMPARE(doc.characterAt(length + 1), QChar());
for (int i = 0; i < text.size(); ++i) {
QChar c = text.at(i);
if (c == QLatin1Char('\n'))
c = QChar(QChar::ParagraphSeparator);
QCOMPARE(doc.characterAt(i), c);
}
}
void tst_QTextDocument::revisions()
{
QTextDocument doc;
QTextCursor cursor(&doc);
QString text("Hello World");
QCOMPARE(doc.firstBlock().revision(), 0);
cursor.insertText(text);
QCOMPARE(doc.firstBlock().revision(), 1);
cursor.setPosition(6);
cursor.insertBlock();
QCOMPARE(cursor.block().previous().revision(), 2);
QCOMPARE(cursor.block().revision(), 2);
cursor.insertText("candle");
QCOMPARE(cursor.block().revision(), 3);
cursor.movePosition(QTextCursor::EndOfBlock);
cursor.insertBlock(); // we are at the block end
QCOMPARE(cursor.block().previous().revision(), 3);
QCOMPARE(cursor.block().revision(), 4);
cursor.insertText("lightbulb");
QCOMPARE(cursor.block().revision(), 5);
cursor.movePosition(QTextCursor::StartOfBlock);
cursor.insertBlock(); // we are the block start
QCOMPARE(cursor.block().previous().revision(), 6);
QCOMPARE(cursor.block().revision(), 5);
}
void tst_QTextDocument::revisionWithUndoCompressionAndUndo()
{
QTextDocument doc;
QTextCursor cursor(&doc);
cursor.insertText("This is the beginning of it all.");
QCOMPARE(doc.firstBlock().revision(), 1);
QCOMPARE(doc.revision(), 1);
cursor.insertBlock();
QCOMPARE(doc.revision(), 2);
cursor.insertText("this");
QCOMPARE(doc.revision(), 3);
cursor.insertText("is");
QCOMPARE(doc.revision(), 4);
cursor.insertText("compressed");
QCOMPARE(doc.revision(), 5);
doc.undo();
QCOMPARE(doc.revision(), 6);
QCOMPARE(doc.toPlainText(), QString("This is the beginning of it all.\n")) ;
cursor.setPosition(0);
QCOMPARE(doc.firstBlock().revision(), 1);
cursor.insertText("Very beginnig");
QCOMPARE(doc.firstBlock().revision(), 7);
doc.undo();
QCOMPARE(doc.revision(), 8);
QCOMPARE(doc.firstBlock().revision(), 1);
cursor.beginEditBlock();
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertText("world");
cursor.endEditBlock();
QCOMPARE(doc.revision(), 9);
doc.undo();
QCOMPARE(doc.revision(), 10);
}
void tst_QTextDocument::testUndoCommandAdded()
{
QVERIFY(doc);
QSignalSpy spy(doc, SIGNAL(undoCommandAdded()));
QVERIFY(spy.isValid());
QVERIFY(spy.isEmpty());
cursor.insertText("a");
QCOMPARE(spy.size(), 1);
cursor.insertText("b"); // should be merged
QCOMPARE(spy.size(), 1);
cursor.insertText("c"); // should be merged
QCOMPARE(spy.size(), 1);
QCOMPARE(doc->toPlainText(), QString("abc"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
doc->clear();
spy.clear();
cursor.insertText("aaa");
QCOMPARE(spy.size(), 1);
spy.clear();
cursor.insertText("aaaa\nbcd");
QCOMPARE(spy.size(), 1);
spy.clear();
cursor.beginEditBlock();
cursor.insertText("aa");
cursor.insertText("bbb\n");
cursor.setCharFormat(QTextCharFormat());
cursor.insertText("\nccc");
QVERIFY(spy.isEmpty());
cursor.endEditBlock();
QCOMPARE(spy.size(), 1);
spy.clear();
cursor.insertBlock();
QCOMPARE(spy.size(), 1);
spy.clear();
cursor.setPosition(5);
QVERIFY(spy.isEmpty());
cursor.setCharFormat(QTextCharFormat());
QVERIFY(spy.isEmpty());
cursor.setPosition(10, QTextCursor::KeepAnchor);
QVERIFY(spy.isEmpty());
QTextCharFormat cf;
cf.setFontItalic(true);
cursor.mergeCharFormat(cf);
QCOMPARE(spy.size(), 1);
spy.clear();
doc->undo();
QCOMPARE(spy.size(), 0);
doc->undo();
QCOMPARE(spy.size(), 0);
spy.clear();
doc->redo();
QCOMPARE(spy.size(), 0);
doc->redo();
QCOMPARE(spy.size(), 0);
}
void tst_QTextDocument::testUndoBlocks()
{
QVERIFY(doc);
cursor.insertText("Hello World");
cursor.insertText("period");
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
cursor.insertText("Hello World");
cursor.insertText("One\nTwo\nThree");
QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("Hello World"));
cursor.insertText("One\nTwo\nThree");
cursor.insertText("Trailing text");
doc->undo();
QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("Hello World"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
cursor.insertText("town");
cursor.beginEditBlock(); // Edit block 1 - Deletion/Insertion
cursor.setPosition(0, QTextCursor::KeepAnchor);
cursor.insertText("r");
cursor.endEditBlock();
cursor.insertText("est"); // Merged into edit block 1
QCOMPARE(doc->toPlainText(), QString("rest"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("town"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
// This case would not happen in practice. If the user typed out this text, it would all be part of one
// edit block. This would cause the undo to clear all text. But for the purpose of testing the beginEditBlock
// and endEditBlock calls with respect to qtextdocument this is tested.
cursor.insertText("quod");
cursor.beginEditBlock(); // Edit block 1 - Insertion
cursor.insertText(" erat");
cursor.endEditBlock();
cursor.insertText(" demonstrandum"); // Merged into edit block 1
QCOMPARE(doc->toPlainText(), QString("quod erat demonstrandum"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("quod"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
}
class Receiver : public QObject
{
Q_OBJECT
public:
QString first;
public slots:
void cursorPositionChanged() {
if (first.isEmpty())
first = QLatin1String("cursorPositionChanged");
}
void contentsChange() {
if (first.isEmpty())
first = QLatin1String("contentsChanged");
}
};
void tst_QTextDocument::receiveCursorPositionChangedAfterContentsChange()
{
QVERIFY(doc);
doc->setDocumentLayout(new MyAbstractTextDocumentLayout(doc));
Receiver rec;
connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)),
&rec, SLOT(cursorPositionChanged()));
connect(doc, SIGNAL(contentsChange(int,int,int)),
&rec, SLOT(contentsChange()));
cursor.insertText("Hello World");
QCOMPARE(rec.first, QString("contentsChanged"));
}
void tst_QTextDocument::QTBUG25778_pixelSizeFromHtml()
{
QTextDocument document1;
QTextDocument document2;
document1.setHtml("<span style=\"font-size: 24px\">Foobar</span>");
document2.setHtml(document1.toHtml());
QTextCursor cursor(&document2);
QCOMPARE(cursor.charFormat().font().pixelSize(), 24);
}
void tst_QTextDocument::copiedFontSize()
{
QTextDocument documentInput;
QTextDocument documentOutput;
QFont fontInput;
fontInput.setPixelSize(24);
QTextCursor cursorInput(&documentInput);
QTextCharFormat formatInput = cursorInput.charFormat();
formatInput.setFont(fontInput);
cursorInput.insertText("Should be the same font", formatInput);
cursorInput.select(QTextCursor::Document);
QTextDocumentFragment fragmentInput(cursorInput);
QString html = fragmentInput.toHtml();
QTextCursor cursorOutput(&documentOutput);
QTextDocumentFragment fragmentOutput = QTextDocumentFragment::fromHtml(html);
cursorOutput.insertFragment(fragmentOutput);
QCOMPARE(cursorOutput.charFormat().font().pixelSize(), 24);
}
void tst_QTextDocument::htmlExportImportBlockCount()
{
QTextDocument document;
{
QTextCursor cursor(&document);
cursor.insertText("Foo");
cursor.insertBlock();
cursor.insertBlock();
cursor.insertBlock();
cursor.insertBlock();
cursor.insertText("Bar");
}
QCOMPARE(document.blockCount(), 5);
QString html = document.toHtml();
document.clear();
document.setHtml(html);
QCOMPARE(document.blockCount(), 5);
}
void tst_QTextDocument::QTBUG27354_spaceAndSoftSpace()
{
QTextDocument document;
{
QTextCursor cursor(&document);
QTextBlockFormat blockFormat;
blockFormat.setAlignment(Qt::AlignJustify);
cursor.mergeBlockFormat(blockFormat);
cursor.insertText("ac");
cursor.insertBlock();
cursor.insertText(" ");
cursor.insertText(QChar(0x2028));
}
// Trigger justification of text
QImage image(1000, 1000, QImage::Format_ARGB32);
image.fill(0);
{
QPainter p(&image);
document.drawContents(&p, image.rect());
}
{
// If no p tag is specified it should not be inheriting it
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">p { line-height: 200% }</style></head><body>Foo<ul><li>First</li></ul></body></html>");
QTextBlock block = td.begin();
while (block.isValid()) {
QTextBlockFormat fmt = block.blockFormat();
QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::SingleHeight));
QCOMPARE(fmt.lineHeight(), qreal(0));
block = block.next();
}
}
{
QTextDocument td;
td.setHtml("<html><head></head><body><p>Foo</p><ul><li>First</li></ul></body></html>");
QList<double> originalMargins;
QTextBlock block = td.begin();
while (block.isValid()) {
originalMargins << block.blockFormat().topMargin();
block = block.next();
}
originalMargins[0] = 85;
td.setHtml("<html><head><style type=\"text/css\">body { margin-top: 85px; }</style></head><body><p>Foo</p><ul><li>First</li></ul></body></html>");
block = td.begin();
int count = 0;
while (block.isValid()) {
QTextBlockFormat fmt = block.blockFormat();
QCOMPARE(fmt.topMargin(), originalMargins.at(count++));
block = block.next();
}
}
}
class BaseDocument : public QTextDocument
{
public:
QUrl loadedResource() const { return resourceUrl; }
QVariant loadResource(int type, const QUrl &name) override
{
resourceUrl = name;
return QTextDocument::loadResource(type, name);
}
private:
QUrl resourceUrl;
};
void tst_QTextDocument::baseUrl_data()
{
QTest::addColumn<QUrl>("base");
QTest::addColumn<QUrl>("resource");
QTest::addColumn<QUrl>("loaded");
QTest::newRow("1") << QUrl() << QUrl("images/logo.png") << QUrl("images/logo.png");
QTest::newRow("2") << QUrl("file:///path/to/content") << QUrl("images/logo.png") << QUrl("file:///path/to/images/logo.png");
QTest::newRow("3") << QUrl("file:///path/to/content/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
QTest::newRow("4") << QUrl("file:///path/to/content/images") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
QTest::newRow("5") << QUrl("file:///path/to/content/images/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/images/logo.png");
QTest::newRow("6") << QUrl("file:///path/to/content/images") << QUrl("../images/logo.png") << QUrl("file:///path/to/images/logo.png");
QTest::newRow("7") << QUrl("file:///path/to/content/images/") << QUrl("../images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
QTest::newRow("8") << QUrl("file:///path/to/content/index.html") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
}
void tst_QTextDocument::baseUrl()
{
QFETCH(QUrl, base);
QFETCH(QUrl, resource);
QFETCH(QUrl, loaded);
BaseDocument document;
QVERIFY(!document.baseUrl().isValid());
document.setBaseUrl(base);
QCOMPARE(document.baseUrl(), base);
document.setHtml(QLatin1String("<img src='") + resource.toString() + QLatin1String("'/>"));
document.resource(QTextDocument::ImageResource, resource);
QCOMPARE(document.loadedResource(), loaded);
}
void tst_QTextDocument::QTBUG28998_linkColor()
{
QPalette pal;
pal.setColor(QPalette::Link, QColor("tomato"));
QGuiApplication::setPalette(pal);
QTextDocument doc;
doc.setHtml("<a href=\"http://www.qt-project.org\">Qt</a>");
QCOMPARE(doc.blockCount(), 1);
QTextBlock block = doc.firstBlock();
QVERIFY(block.isValid());
QTextFragment fragment = block.begin().fragment();
QVERIFY(fragment.isValid());
QTextCharFormat format = fragment.charFormat();
QVERIFY(format.isValid());
QVERIFY(format.isAnchor());
QCOMPARE(format.anchorHref(), QStringLiteral("http://www.qt-project.org"));
QCOMPARE(format.foreground(), pal.link());
}
class ContentsChangeHandler : public QObject
{
Q_OBJECT
public:
ContentsChangeHandler(QTextDocument *doc)
: verticalMovementX(-1)
, doc(doc)
{
connect(doc, SIGNAL(contentsChange(int,int,int)),
this, SLOT(saveModifiedText(int, int, int)));
}
private slots:
void saveModifiedText(int from, int /*charsRemoved*/, int charsAdded)
{
QTextCursor tmp(doc);
tmp.setPosition(from);
tmp.setPosition(from + charsAdded, QTextCursor::KeepAnchor);
text = tmp.selectedText();
verticalMovementX = tmp.verticalMovementX();
}
public:
QString text;
int verticalMovementX;
private:
QTextDocument *doc;
};
void tst_QTextDocument::textCursorUsageWithinContentsChange()
{
// force creation of layout
doc->documentLayout();
QTextCursor cursor(doc);
cursor.insertText("initial text");
ContentsChangeHandler handler(doc);
cursor.insertText("new text");
QCOMPARE(handler.text, QString("new text"));
QCOMPARE(handler.verticalMovementX, -1);
}
void tst_QTextDocument::cssInheritance()
{
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200% }</style></head><body>"
"<p>Foo</p><p>Bar</p><p>Baz</p></body></html>");
QTextBlock block = td.begin();
while (block.isValid()) {
QTextBlockFormat fmt = block.blockFormat();
QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(fmt.lineHeight(), qreal(200));
block = block.next();
}
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200% } p { line-height: 300% }</style></head><body>"
"<p style=\"line-height: 40px\">Foo</p><p>Bar</p><p>Baz</p></body></html>");
QTextBlock block = td.begin();
QTextBlockFormat fmt = block.blockFormat();
QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::MinimumHeight));
QCOMPARE(fmt.lineHeight(), qreal(40));
block = block.next();
fmt = block.blockFormat();
QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(fmt.lineHeight(), qreal(300));
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { font-weight: bold; background-color: #ff0000 }</style></head><body>"
"<p>Foo</p><p>Bar</p><p>Baz</p></body></html>");
QTextBlock block = td.begin();
while (block.isValid()) {
QCOMPARE(block.blockFormat().background(), QBrush());
QVERIFY(block.charFormat().font().bold());
block = block.next();
}
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { font-style: italic; font-weight: normal; }</style></head><body>"
"<table><tr><th>Foo</th></tr><tr><td>Bar</td></tr></table></body></html>");
QTextBlock block = td.begin();
// First is the table
QTextCharFormat fmt = block.charFormat();
QVERIFY(!fmt.font().bold());
QVERIFY(fmt.font().italic());
// Then the th
block = block.next();
fmt = block.charFormat();
QVERIFY(fmt.font().bold());
QVERIFY(fmt.font().italic());
// Then the td
block = block.next();
fmt = block.charFormat();
QVERIFY(!fmt.font().bold());
QVERIFY(fmt.font().italic());
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">b { font-style: italic; font-weight: normal; }</style></head><body>"
"<p>This should be <b>bold</b></p></body></html>");
QTextBlock block = td.begin();
// First is the p
QTextCharFormat fmt = block.charFormat();
QVERIFY(!fmt.font().bold());
QTextBlock::iterator it = block.begin();
// The non bold text is first
QTextFragment currentFragment = it.fragment();
QVERIFY(currentFragment.isValid());
fmt = currentFragment.charFormat();
QVERIFY(!fmt.font().bold());
++it;
QVERIFY(!it.atEnd());
// Now check the "bold" text
currentFragment = it.fragment();
QVERIFY(currentFragment.isValid());
fmt = currentFragment.charFormat();
QVERIFY(!fmt.font().bold());
QVERIFY(fmt.font().italic());
}
{
QTextDocument td;
td.setHtml("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"test.css\"></head><body>"
"<p>This should be <b>bold</b></p></body></html>");
QTextBlock block = td.begin();
// First is the p
QTextCharFormat fmt = block.charFormat();
QVERIFY(!fmt.font().bold());
QTextBlock::iterator it = block.begin();
// The non bold text is first
QTextFragment currentFragment = it.fragment();
QVERIFY(currentFragment.isValid());
fmt = currentFragment.charFormat();
QVERIFY(!fmt.font().bold());
++it;
QVERIFY(!it.atEnd());
// Now check the bold text
currentFragment = it.fragment();
QVERIFY(currentFragment.isValid());
fmt = currentFragment.charFormat();
QVERIFY(fmt.font().bold());
}
}
void tst_QTextDocument::lineHeightType()
{
{
QTextDocument td;
td.setHtml("<html><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight));
QCOMPARE(format.lineHeight(), 0.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 40px; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight));
QCOMPARE(format.lineHeight(), 40.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200%; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(format.lineHeight(), 200.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200%; -qt-line-height-type: single; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight));
QCOMPARE(format.lineHeight(), 200.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 40px; -qt-line-height-type: proportional; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(format.lineHeight(), 40.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 10; -qt-line-height-type: fixed; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
QCOMPARE(format.lineHeight(), 10.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: fixed; line-height: 10; -qt-line-height-type: fixed; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
QCOMPARE(format.lineHeight(), 10.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: proportional; line-height: 3; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(format.lineHeight(), 3.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 2.5; -qt-line-height-type: proportional; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(format.lineHeight(), 2.5);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 33; -qt-line-height-type: minimum; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight));
QCOMPARE(format.lineHeight(), 33.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: fixed; line-height: 200%; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
QCOMPARE(format.lineHeight(), 200.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: fixed; line-height: 200px; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
QCOMPARE(format.lineHeight(), 200.0);
}
}
void tst_QTextDocument::cssLineHeightMultiplier()
{
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body { line-height: 10; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(format.lineHeight(), 1000.0);
}
{
QTextDocument td;
td.setHtml("<html><head><style type=\"text/css\">body {line-height: 1.38; }</style></head><body>Foobar</body></html>");
QTextBlock block = td.begin();
QTextBlockFormat format = block.blockFormat();
QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
QCOMPARE(format.lineHeight(), 138.0);
}
}
void tst_QTextDocument::fontTagFace()
{
{
QTextDocument td;
td.setHtml("<html><body><font face='Times'>Foobar</font></body></html>");
QTextFragment fragment = td.begin().begin().fragment();
QTextCharFormat format = fragment.charFormat();
QCOMPARE(format.fontFamilies().toStringList().value(0, QString()), QLatin1String("Times"));
}
{
QTextDocument td;
td.setHtml("<html><body><font face='Times, serif'>Foobar</font></body></html>");
QTextFragment fragment = td.begin().begin().fragment();
QTextCharFormat format = fragment.charFormat();
QCOMPARE(format.fontFamilies().toStringList().value(0, QString()), QLatin1String("Times"));
QStringList expectedFamilies = { QLatin1String("Times"), QLatin1String("serif") };
QCOMPARE(format.fontFamilies().toStringList(), expectedFamilies);
}
}
void tst_QTextDocument::mergeFontFamilies()
{
QTextDocument td;
td.setHtml(QLatin1String(
"<html><body>"
"<span style=\" font-family:'MS Shell Dlg 2';\">Hello world</span>"
"</body></html>"));
QTextCharFormat newFormat;
newFormat.setFontFamilies({QLatin1String("Jokerman")});
QTextCursor cursor = QTextCursor(&td);
cursor.setPosition(0);
cursor.setPosition(QByteArray("Hello World").size(), QTextCursor::KeepAnchor);
cursor.mergeCharFormat(newFormat);
QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Jokerman';")));
QTextCharFormat newFormatFamilies;
newFormatFamilies.setFontFamilies({ QLatin1String("Arial"), QLatin1String("Helvetica") });
cursor.mergeCharFormat(newFormatFamilies);
QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Arial','Helvetica'")));
newFormatFamilies.setFontFamilies({ QLatin1String("Arial"), QLatin1String("Jokerman"), QLatin1String("Helvetica") });
cursor.mergeCharFormat(newFormatFamilies);
QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Arial','Jokerman','Helvetica'")));
}
void tst_QTextDocument::clearUndoRedoStacks()
{
QTextDocument doc;
QTextCursor c(&doc);
c.insertText(QStringLiteral("lorem ipsum"));
QVERIFY(doc.isUndoAvailable());
doc.clearUndoRedoStacks(QTextDocument::UndoStack); // Don't crash
QVERIFY(!doc.isUndoAvailable());
}
void tst_QTextDocument::resourceProvider()
{
int providerCalled = 0;
QUrl providerUrl;
auto provider = [&](const QUrl &url){
providerUrl = url;
++providerCalled;
return QVariant(42);
};
const QUrl url("test://img");
const QString html = QLatin1String("<img src='%1'/>").arg(url.toString());
QTextDocument doc;
QVERIFY(!doc.resourceProvider());
doc.setResourceProvider(provider);
QVERIFY(doc.resourceProvider());
doc.setHtml(html);
const QVariant res = doc.resource(QTextDocument::UserResource, url);
QVERIFY(res.isValid());
QCOMPARE(providerUrl, url);
QCOMPARE(providerCalled, 1);
QVERIFY(!QTextDocument::defaultResourceProvider());
QTextDocument::setDefaultResourceProvider(provider);
QVERIFY(QTextDocument::defaultResourceProvider());
QTextDocument doc2;
QVERIFY(!doc2.resourceProvider());
doc2.setHtml(html);
QVariant res2 = doc2.resource(QTextDocument::UserResource, url);
QCOMPARE(res2, res);
QCOMPARE(providerCalled, 2);
}
void tst_QTextDocument::contentsChangeIndices_data()
{
QTest::addColumn<QString>("html");
// adding list entries change the entire block, so change position is
// not the same as the cursor position if this value is >= 0
QTest::addColumn<int>("expectedBegin");
QTest::addRow("text") << "Test" << -1;
QTest::addRow("unnumbered list") << "<ul><li>Test</li></ul>" << 0;
QTest::addRow("numbered list") << "<ol><li>Test</li></ol>" << 0;
QTest::addRow("table") << "<table><tr><td>Test</td></tr></table>" << -1;
}
void tst_QTextDocument::contentsChangeIndices()
{
QFETCH(QString, html);
QFETCH(int, expectedBegin);
QTextDocument doc;
QTestDocumentLayout *layout = new QTestDocumentLayout(&doc);
doc.setDocumentLayout(layout);
doc.setHtml(QString("<html><body>%1</body></html>").arg(html));
int documentLength = 0;
int cursorLength = 0;
int changeBegin = 0;
int changeRemoved = 0;
int changeAdded = 0;
connect(&doc, &QTextDocument::contentsChange, this, [&](int pos, int removed, int added){
documentLength = doc.characterCount();
QTextCursor cursor(&doc);
cursor.movePosition(QTextCursor::End);
// includes end-of-paragraph character
cursorLength = cursor.position() + 1;
changeBegin = pos;
changeRemoved = removed;
changeAdded = added;
});
QTextCursor cursor(&doc);
cursor.movePosition(QTextCursor::End);
if (expectedBegin < 0)
expectedBegin = cursor.position();
cursor.insertBlock();
const int changeEnd = changeBegin + changeAdded;
QVERIFY(documentLength > 0);
QCOMPARE(documentLength, cursorLength);
QVERIFY(documentLength >= changeEnd);
QCOMPARE(changeBegin, expectedBegin);
QCOMPARE(changeAdded - changeRemoved, 1);
}
void tst_QTextDocument::insertHtmlWithComments_data()
{
QTest::addColumn<QString>("html");
QTest::addColumn<QStringList>("expectedBlocks");
QTest::newRow("commentless") << "<p>first</p><p>second</p><p>third</p>"
<< QStringList { "first", "second", "third" };
QTest::newRow("normal") << "<p>first</p><!--<p>second</p>--><p>third</p>"
<< QStringList { "first", "third" };
QTest::newRow("nonClosing") << "<p>first</p><!--<p>second</p><p>third</p>"
<< QStringList { "first" };
QTest::newRow("immediatelyClosing") << "<p>first</p><!----><p>second</p><p>third</p>"
<< QStringList { "first", "second", "third" };
QTest::newRow("fake") << "<p>first</p><!-<p>second</p><p>third</p>"
<< QStringList { "first", "second", "third" };
QTest::newRow("endingNonExistant") << "<p>first</p>--><p>second</p><p>third</p>"
<< QStringList { "first", "-->", "second", "third" };
}
void tst_QTextDocument::insertHtmlWithComments()
{
QFETCH(QString, html);
QFETCH(QStringList, expectedBlocks);
QTextDocument doc;
doc.setHtml(html);
QCOMPARE(doc.blockCount(), expectedBlocks.size());
QStringList blockContent;
auto currentBlock = doc.begin();
while (currentBlock != doc.end()) {
blockContent.append(currentBlock.text());
currentBlock = currentBlock.next();
}
QCOMPARE(blockContent, expectedBlocks);
}
void tst_QTextDocument::delayedLayout()
{
QTextDocument doc;
doc.setHtml("<html>Foobar</html>");
QCOMPARE(doc.blockCount(), 1);
doc.setLayoutEnabled(false);
// Force creation of a layout
QVERIFY(doc.documentLayout());
QTextBlock block = doc.begin();
QTextLayout *layout = block.layout();
QCOMPARE(layout->lineCount(), 0); // layout didn't happen yet
doc.setLayoutEnabled(true);
QCOMPARE(layout->lineCount(), 1); // layout happened
}
QTEST_MAIN(tst_QTextDocument)
#include "tst_qtextdocument.moc"