qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,43 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qabstracttextdocumentlayout)
add_subdirectory(qfont)
add_subdirectory(qfontdatabase)
add_subdirectory(qfontmetrics)
add_subdirectory(qglyphrun)
add_subdirectory(qrawfont)
add_subdirectory(qstatictext)
add_subdirectory(qsyntaxhighlighter)
add_subdirectory(qtextblock)
add_subdirectory(qtextcursor)
add_subdirectory(qtextdocumentfragment)
add_subdirectory(qtextdocumentlayout)
add_subdirectory(qtextformat)
add_subdirectory(qtextimagehandler)
add_subdirectory(qtextlist)
add_subdirectory(qtextobject)
# add_subdirectory(qtextscriptengine) # disable until system_harfbuzz feature is available
add_subdirectory(qtexttable)
add_subdirectory(qinputcontrol)
if(QT_FEATURE_private_tests AND TARGET Qt::Xml)
add_subdirectory(qcssparser)
endif()
if(QT_FEATURE_private_tests)
add_subdirectory(qfontcache)
add_subdirectory(qtextlayout)
add_subdirectory(qzip)
add_subdirectory(qtextodfwriter)
endif()
if(TARGET Qt::Xml)
add_subdirectory(qtextdocument)
endif()
if(QT_FEATURE_private_tests AND UNIX)
add_subdirectory(qtextpiecetable)
endif()
if(QT_FEATURE_textmarkdownreader)
add_subdirectory(qtextmarkdownimporter)
endif()
if(QT_FEATURE_private_tests AND QT_FEATURE_textmarkdownwriter)
add_subdirectory(qtextmarkdownwriter)
endif()

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qabstracttextdocumentlayout Test:
#####################################################################
qt_internal_add_test(tst_qabstracttextdocumentlayout
SOURCES
tst_qabstracttextdocumentlayout.cpp
LIBRARIES
Qt::Gui
)

View File

@ -0,0 +1,209 @@
// 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 <qcoreapplication.h>
#include <qdebug.h>
#include <qabstracttextdocumentlayout.h>
#include <qimage.h>
#include <qtextobject.h>
#include <qfontmetrics.h>
class tst_QAbstractTextDocumentLayout : public QObject
{
Q_OBJECT
public:
tst_QAbstractTextDocumentLayout();
virtual ~tst_QAbstractTextDocumentLayout();
private slots:
void getSetCheck();
void maximumBlockCount();
void anchorAt();
void imageAt();
void formatAt();
};
tst_QAbstractTextDocumentLayout::tst_QAbstractTextDocumentLayout()
{
}
tst_QAbstractTextDocumentLayout::~tst_QAbstractTextDocumentLayout()
{
}
class MyAbstractTextDocumentLayout : public QAbstractTextDocumentLayout
{
Q_OBJECT
public:
MyAbstractTextDocumentLayout(QTextDocument *doc)
: QAbstractTextDocumentLayout(doc)
, gotFullLayout(false)
, blockCount(0)
, changeEvents(0)
{
}
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 from, int /* oldLength */, int length) override
{
++changeEvents;
QTextBlock last = document()->lastBlock();
int lastPos = last.position() + last.length() - 1;
if (from == 0 && length == lastPos)
gotFullLayout = true;
}
bool gotFullLayout;
int blockCount;
int changeEvents;
public slots:
void blockCountChanged(int bc) { blockCount = bc; }
};
// Testing get/set functions
void tst_QAbstractTextDocumentLayout::getSetCheck()
{
QTextDocument doc;
MyAbstractTextDocumentLayout obj1(&doc);
// QPaintDevice * QAbstractTextDocumentLayout::paintDevice()
// void QAbstractTextDocumentLayout::setPaintDevice(QPaintDevice *)
QImage *var1 = new QImage(QSize(10,10), QImage::Format_ARGB32_Premultiplied);
obj1.setPaintDevice(var1);
QCOMPARE(static_cast<QPaintDevice *>(var1), obj1.paintDevice());
obj1.setPaintDevice((QPaintDevice *)0);
QCOMPARE(static_cast<QPaintDevice *>(0), obj1.paintDevice());
delete var1;
}
void tst_QAbstractTextDocumentLayout::maximumBlockCount()
{
QTextDocument doc;
doc.setMaximumBlockCount(10);
MyAbstractTextDocumentLayout layout(&doc);
doc.setDocumentLayout(&layout);
QObject::connect(&doc, SIGNAL(blockCountChanged(int)), &layout, SLOT(blockCountChanged(int)));
QTextCursor cursor(&doc);
for (int i = 0; i < 10; ++i) {
cursor.insertBlock();
cursor.insertText("bla");
}
QCOMPARE(layout.blockCount, 10);
layout.gotFullLayout = false;
layout.changeEvents = 0;
cursor.insertBlock();
QCOMPARE(layout.changeEvents, 2);
cursor.insertText("foo");
QCOMPARE(layout.changeEvents, 3);
cursor.insertBlock();
QCOMPARE(layout.changeEvents, 5);
cursor.insertText("foo");
QCOMPARE(layout.changeEvents, 6);
QVERIFY(!layout.gotFullLayout);
QCOMPARE(layout.blockCount, 10);
}
void tst_QAbstractTextDocumentLayout::anchorAt()
{
QTextDocument doc;
doc.setHtml("<a href=\"link\">foo</a>");
QAbstractTextDocumentLayout *documentLayout = doc.documentLayout();
QTextBlock firstBlock = doc.begin();
QTextLayout *layout = firstBlock.layout();
layout->setPreeditArea(doc.toPlainText().size(), "xxx");
doc.setPageSize(QSizeF(1000, 1000));
QFontMetrics metrics(layout->font());
QPointF blockStart = documentLayout->blockBoundingRect(firstBlock).topLeft();
// anchorAt on start returns link
QRect linkBr = metrics.boundingRect("foo");
QPointF linkPoint((linkBr.width() / 2) + blockStart.x(), (linkBr.height() / 2) + blockStart.y());
QCOMPARE(documentLayout->anchorAt(linkPoint), QString("link"));
// anchorAt() on top of preedit at end should not assert
QRect preeditBr = metrics.boundingRect(doc.toPlainText() + "xx");
QPointF preeditPoint(preeditBr.width() + blockStart.x(), (preeditBr.height() / 2) + blockStart.y());
QCOMPARE(documentLayout->anchorAt(preeditPoint), QString());
// preedit at start should not return link
layout->setPreeditArea(0, "xxx");
preeditBr = metrics.boundingRect("xx");
preeditPoint = QPointF(preeditBr.width() + blockStart.x(), (preeditBr.height() / 2) + blockStart.y());
QCOMPARE(documentLayout->anchorAt(preeditPoint), QString());
}
void tst_QAbstractTextDocumentLayout::imageAt()
{
QTextDocument doc;
doc.setHtml("foo<a href=\"link\"><img src=\"image\" width=\"50\" height=\"50\"/></a>");
QAbstractTextDocumentLayout *documentLayout = doc.documentLayout();
QTextBlock firstBlock = doc.begin();
QTextLayout *layout = firstBlock.layout();
layout->setPreeditArea(doc.toPlainText().size(), "xxx");
doc.setPageSize(QSizeF(1000, 1000));
QFontMetrics metrics(layout->font());
QPointF blockStart = documentLayout->blockBoundingRect(firstBlock).topLeft();
QRect fooBr = metrics.boundingRect("foo");
QPointF imagePoint(fooBr.width() + blockStart.x() + 25, blockStart.y() + 25);
// imageAt on image returns source
QCOMPARE(documentLayout->imageAt(imagePoint), QString("image"));
// anchorAt on image returns link
QCOMPARE(documentLayout->anchorAt(imagePoint), QString("link"));
// imageAt on start returns nothing (there's the "foo" text)
QPointF fooPoint(blockStart.x() + (fooBr.width() / 2), (fooBr.height() / 2) + blockStart.y());
QCOMPARE(documentLayout->imageAt(fooPoint), QString());
}
void tst_QAbstractTextDocumentLayout::formatAt()
{
QTextDocument doc;
doc.setHtml("foo<i><a href=\"link\"><img src=\"image\" width=\"50\" height=\"50\"/></a></i>");
QAbstractTextDocumentLayout *documentLayout = doc.documentLayout();
QTextBlock firstBlock = doc.begin();
QTextLayout *layout = firstBlock.layout();
layout->setPreeditArea(doc.toPlainText().size(), "xxx");
doc.setPageSize(QSizeF(1000, 1000));
QFontMetrics metrics(layout->font());
QPointF blockStart = documentLayout->blockBoundingRect(firstBlock).topLeft();
QRect fooBr = metrics.boundingRect("foo");
QPointF imagePoint(fooBr.width() + blockStart.x() + 25, blockStart.y() + 25);
QTextFormat format = documentLayout->formatAt(imagePoint);
QVERIFY(format.isCharFormat());
QVERIFY(format.toCharFormat().isAnchor());
QVERIFY(format.toCharFormat().fontItalic());
QVERIFY(format.isImageFormat());
// move over the unformatted "foo" text)
QPointF fooPoint(blockStart.x() + (fooBr.width() / 2), (fooBr.height() / 2) + blockStart.y());
format = documentLayout->formatAt(fooPoint);
QVERIFY(format.isCharFormat());
QVERIFY(!format.toCharFormat().isAnchor());
QVERIFY(!format.toCharFormat().fontItalic());
QVERIFY(!format.isImageFormat());
}
QTEST_MAIN(tst_QAbstractTextDocumentLayout)
#include "tst_qabstracttextdocumentlayout.moc"

View File

@ -0,0 +1,22 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qcssparser Test:
#####################################################################
# Collect test data
file(GLOB_RECURSE test_data
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
testdata/*
)
qt_internal_add_test(tst_qcssparser
SOURCES
tst_qcssparser.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
Qt::Xml
TESTDATA ${test_data}
)

View File

@ -0,0 +1 @@
/* let's see if comments actually work *//*foo*/ "it /*should be preserved \"in strings*/ though"

View File

@ -0,0 +1,4 @@
S|/* let's see if comments actually work */
S|/*foo*/
S|
STRING|"it /*should be preserved "in strings*/ though"

View File

@ -0,0 +1 @@
/*foo*/{/*foo*/+/*foo*/>/*foo*/,/*foo*/}/*foo*/-

View File

@ -0,0 +1,12 @@
S|/*foo*/
LBRACE|{
S|/*foo*/
PLUS|+
S|/*foo*/
GREATER|>
S|/*foo*/
COMMA|,
S|/*foo*/
RBRACE|}
S|/*foo*/
MINUS|-

View File

@ -0,0 +1 @@
url(/*comment*/"www.kde.org")

View File

@ -0,0 +1,4 @@
FUNCTION|url(
S|/*comment*/
STRING|"www.kde.org"
RPAREN|)

View File

@ -0,0 +1 @@
!/*hmm*/important

View File

@ -0,0 +1,3 @@
EXCLAMATION_SYM|!
S|/*hmm*/
IDENT|important

View File

@ -0,0 +1 @@
background: 'test_bug.png';

View File

@ -0,0 +1,5 @@
IDENT|background
COLON|:
S|
STRING|'test_bug.png'
SEMICOLON|;

View File

@ -0,0 +1 @@
p { display:block; }

View File

@ -0,0 +1,9 @@
IDENT|p
LBRACE| {
S|
IDENT|display
COLON|:
IDENT|block
SEMICOLON|;
S|
RBRACE|}

View File

@ -0,0 +1 @@
\41"\7E"\00006Df

View File

@ -0,0 +1,3 @@
IDENT|A
STRING|"~"
IDENT|mf

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
[defaultFamily:cursive]
centos
b2qt
rhel
[defaultFamily:fantasy]
centos
b2qt
rhel

View File

@ -0,0 +1,32 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qfont Test:
#####################################################################
# Resources:
set(testfont_resource_files
"datastream.515"
"weirdfont.otf"
)
qt_internal_add_test(tst_qfont
SOURCES
tst_qfont.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
Qt::TestPrivate
TESTDATA ${testfont_resource_files}
BUILTIN_TESTDATA
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qfont CONDITION TARGET Qt::Widgets
LIBRARIES
Qt::Widgets
)

Binary file not shown.

View File

@ -0,0 +1,845 @@
// 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 <QBuffer>
#include <QtEndian>
#if QT_CONFIG(process)
#include <QProcess>
#endif
#include <qfont.h>
#include <private/qfont_p.h>
#include <qfontdatabase.h>
#include <qfontinfo.h>
#include <qstringlist.h>
#include <qguiapplication.h>
#ifndef QT_NO_WIDGETS
#include <qwidget.h>
#endif
#include <qlist.h>
#include <QtTest/private/qemulationdetector_p.h>
class tst_QFont : public QObject
{
Q_OBJECT
private slots:
void getSetCheck();
void exactMatch();
void compare();
void resolve();
#ifndef QT_NO_WIDGETS
void resetFont();
#endif
void isCopyOf();
void italicOblique();
void insertAndRemoveSubstitutions();
void serialize_data();
void serialize();
void deserializeQt515();
void styleName();
void defaultFamily_data();
void defaultFamily();
void toAndFromString();
void fromStringWithoutStyleName();
void fromDegenerateString_data();
void fromDegenerateString();
void sharing();
void familyNameWithCommaQuote_data();
void familyNameWithCommaQuote();
void setFamilies_data();
void setFamilies();
void setFamiliesAndFamily_data();
void setFamiliesAndFamily();
};
// Testing get/set functions
void tst_QFont::getSetCheck()
{
QFont obj1;
// Style QFont::style()
// void QFont::setStyle(Style)
obj1.setStyle(QFont::Style(QFont::StyleNormal));
QCOMPARE(QFont::Style(QFont::StyleNormal), obj1.style());
obj1.setStyle(QFont::Style(QFont::StyleItalic));
QCOMPARE(QFont::Style(QFont::StyleItalic), obj1.style());
obj1.setStyle(QFont::Style(QFont::StyleOblique));
QCOMPARE(QFont::Style(QFont::StyleOblique), obj1.style());
// StyleStrategy QFont::styleStrategy()
// void QFont::setStyleStrategy(StyleStrategy)
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDefault));
QCOMPARE(QFont::StyleStrategy(QFont::PreferDefault), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferBitmap));
QCOMPARE(QFont::StyleStrategy(QFont::PreferBitmap), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDevice));
QCOMPARE(QFont::StyleStrategy(QFont::PreferDevice), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferOutline));
QCOMPARE(QFont::StyleStrategy(QFont::PreferOutline), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::ForceOutline));
QCOMPARE(QFont::StyleStrategy(QFont::ForceOutline), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferMatch));
QCOMPARE(QFont::StyleStrategy(QFont::PreferMatch), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferQuality));
QCOMPARE(QFont::StyleStrategy(QFont::PreferQuality), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferAntialias));
QCOMPARE(QFont::StyleStrategy(QFont::PreferAntialias), obj1.styleStrategy());
obj1.setStyleStrategy(QFont::StyleStrategy(QFont::NoAntialias));
QCOMPARE(QFont::StyleStrategy(QFont::NoAntialias), obj1.styleStrategy());
}
void tst_QFont::exactMatch()
{
QFont font;
// Check if a non-existing font hasn't an exact match
font = QFont( "BogusFont", 33 );
QVERIFY( !font.exactMatch() );
QVERIFY(!QFont("sans").exactMatch());
QVERIFY(!QFont("sans-serif").exactMatch());
QVERIFY(!QFont("serif").exactMatch());
QVERIFY(!QFont("monospace").exactMatch());
font.setFamilies(QStringList() << "BogusFont");
QVERIFY(!font.exactMatch());
QVERIFY(!QFont("sans").exactMatch());
QVERIFY(!QFont("sans-serif").exactMatch());
QVERIFY(!QFont("serif").exactMatch());
QVERIFY(!QFont("monospace").exactMatch());
// Confirm that exactMatch is true for a valid font
const QString family = QFontDatabase::families().first();
const QString style = QFontDatabase::styles(family).first();
const int pointSize = QFontDatabase::pointSizes(family, style).first();
font = QFontDatabase::font(family, style, pointSize);
QVERIFY(font.exactMatch());
if (QFontDatabase::families().contains("Arial")) {
font = QFont("Arial");
QVERIFY(font.exactMatch());
font = QFont(QString());
font.setFamilies({"Arial"});
QVERIFY(font.exactMatch());
}
}
void tst_QFont::italicOblique()
{
QStringList families = QFontDatabase::families();
if (families.isEmpty())
return;
QStringList::ConstIterator f_it, f_end = families.end();
for (f_it = families.begin(); f_it != f_end; ++f_it) {
QString family = *f_it;
QStringList styles = QFontDatabase::styles(family);
QStringList::ConstIterator s_it, s_end = styles.end();
for (s_it = styles.begin(); s_it != s_end; ++s_it) {
QString style = *s_it;
if (QFontDatabase::isSmoothlyScalable(family, style)) {
if (style.contains("Oblique")) {
style.replace("Oblique", "Italic");
} else if (style.contains("Italic")) {
style.replace("Italic", "Oblique");
} else {
continue;
}
QFont f = QFontDatabase::font(family, style, 12);
QVERIFY(f.italic());
}
}
}
}
void tst_QFont::compare()
{
QFont font;
{
QFont font2 = font;
font2.setPointSize(24);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
}
{
QFont font2 = font;
font2.setPixelSize(24);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
}
font.setPointSize(12);
font.setItalic(false);
font.setWeight(QFont::Normal);
font.setUnderline(false);
font.setStrikeOut(false);
font.setOverline(false);
{
QFont font2 = font;
font2.setPointSize(24);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
}
{
QFont font2 = font;
font2.setPixelSize(24);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
}
{
QFont font2 = font;
font2.setItalic(true);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
font2.setItalic(false);
QCOMPARE(font, font2);
QVERIFY(!(font < font2));
font2.setWeight(QFont::Bold);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
font2.setWeight(QFont::Normal);
QCOMPARE(font, font2);
QVERIFY(!(font < font2));
font.setUnderline(true);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
font.setUnderline(false);
QCOMPARE(font, font2);
QVERIFY(!(font < font2));
font.setStrikeOut(true);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
font.setStrikeOut(false);
QCOMPARE(font, font2);
QVERIFY(!(font < font2));
font.setOverline(true);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
font.setOverline(false);
QCOMPARE(font, font2);
QVERIFY(!(font < font2));
font.setCapitalization(QFont::SmallCaps);
QVERIFY(font != font2);
QCOMPARE(font < font2,!(font2 < font));
font.setCapitalization(QFont::MixedCase);
QCOMPARE(font, font2);
QVERIFY(!(font < font2));
}
}
void tst_QFont::resolve()
{
QFont font;
font.setPointSize(font.pointSize() * 2);
font.setItalic(false);
font.setWeight(QFont::Normal);
font.setUnderline(false);
font.setStrikeOut(false);
font.setOverline(false);
font.setStretch(QFont::Unstretched);
QFont font1;
font1.setWeight(QFont::Bold);
QFont font2 = font1.resolve(font);
QCOMPARE(font2.weight(), font1.weight());
QCOMPARE(font2.pointSize(), font.pointSize());
QCOMPARE(font2.italic(), font.italic());
QCOMPARE(font2.underline(), font.underline());
QCOMPARE(font2.overline(), font.overline());
QCOMPARE(font2.strikeOut(), font.strikeOut());
QCOMPARE(font2.stretch(), font.stretch());
QFont font3;
font3.setStretch(QFont::UltraCondensed);
QFont font4 = font3.resolve(font1).resolve(font);
QCOMPARE(font4.stretch(), font3.stretch());
QCOMPARE(font4.weight(), font.weight());
QCOMPARE(font4.pointSize(), font.pointSize());
QCOMPARE(font4.italic(), font.italic());
QCOMPARE(font4.underline(), font.underline());
QCOMPARE(font4.overline(), font.overline());
QCOMPARE(font4.strikeOut(), font.strikeOut());
QFont f1,f2,f3;
f2.setPointSize(45);
f3.setPointSize(55);
QFont f4 = f1.resolve(f2);
QCOMPARE(f4.pointSize(), 45);
f4 = f4.resolve(f3);
QCOMPARE(f4.pointSize(), 55);
QFont font5, font6;
const QStringList fontFamilies = { QStringLiteral("Arial") };
font5.setFamilies(fontFamilies);
font6 = font6.resolve(font5);
QCOMPARE(font6.families(), fontFamilies);
QFont font7, font8;
// This will call setFamilies() directly now
font7.setFamily(QLatin1String("Helvetica"));
font8.setFamilies(fontFamilies);
font7 = font7.resolve(font8);
QCOMPARE(font7.families(), QStringList({"Helvetica"}));
QCOMPARE(font7.family(), "Helvetica");
}
#ifndef QT_NO_WIDGETS
void tst_QFont::resetFont()
{
QWidget parent;
QWidget firstChild(&parent);
QFont parentFont = parent.font();
parentFont.setPointSize(parentFont.pointSize() + 2);
parent.setFont(parentFont);
QFont childFont = firstChild.font();
childFont.setBold(!childFont.bold());
firstChild.setFont(childFont);
QWidget secondChild(&parent);
secondChild.setFont(childFont);
QVERIFY(parentFont.resolveMask() != 0);
QVERIFY(childFont.resolveMask() != 0);
QVERIFY(childFont != parentFont);
// reset font on both children
firstChild.setFont(QFont());
secondChild.setFont(QFont());
QCOMPARE(firstChild.font().resolveMask(), QFont::SizeResolved);
QCOMPARE(secondChild.font().resolveMask(), QFont::SizeResolved);
QCOMPARE(firstChild.font().pointSize(), parent.font().pointSize());
QCOMPARE(secondChild.font().pointSize(), parent.font().pointSize());
QVERIFY(parent.font().resolveMask() != 0);
}
#endif
void tst_QFont::isCopyOf()
{
QFont font;
QVERIFY(font.isCopyOf(QGuiApplication::font()));
QFont font2("bogusfont", 23);
QVERIFY(! font2.isCopyOf(QGuiApplication::font()));
QFont font3 = font;
QVERIFY(font3.isCopyOf(font));
font3.setPointSize(256);
QVERIFY(!font3.isCopyOf(font));
font3.setPointSize(font.pointSize());
QVERIFY(!font3.isCopyOf(font));
}
void tst_QFont::insertAndRemoveSubstitutions()
{
QFont::removeSubstitutions("BogusFontFamily");
// make sure it is empty before we start
QVERIFY(QFont::substitutes("BogusFontFamily").isEmpty());
QVERIFY(QFont::substitutes("bogusfontfamily").isEmpty());
// inserting Foo
QFont::insertSubstitution("BogusFontFamily", "Foo");
QCOMPARE(QFont::substitutes("BogusFontFamily").size(), 1);
QCOMPARE(QFont::substitutes("bogusfontfamily").size(), 1);
// inserting Bar and Baz
QStringList moreFonts;
moreFonts << "Bar" << "Baz";
QFont::insertSubstitutions("BogusFontFamily", moreFonts);
QCOMPARE(QFont::substitutes("BogusFontFamily").size(), 3);
QCOMPARE(QFont::substitutes("bogusfontfamily").size(), 3);
QFont::removeSubstitutions("BogusFontFamily");
// make sure it is empty again
QVERIFY(QFont::substitutes("BogusFontFamily").isEmpty());
QVERIFY(QFont::substitutes("bogusfontfamily").isEmpty());
}
Q_DECLARE_METATYPE(QDataStream::Version)
void tst_QFont::serialize_data()
{
QTest::addColumn<QFont>("font");
// The version in which the tested feature was added.
QTest::addColumn<QDataStream::Version>("minimumStreamVersion");
QFont basicFont;
// Versions <= Qt 2.1 had broken point size serialization,
// so we set an integer point size.
basicFont.setPointSize(9);
// Versions <= Qt 5.4 didn't serialize styleName, so clear it
basicFont.setStyleName(QString());
QFont font = basicFont;
QTest::newRow("defaultConstructed") << font << QDataStream::Qt_1_0;
font.setLetterSpacing(QFont::AbsoluteSpacing, 105);
QTest::newRow("letterSpacing=105") << font << QDataStream::Qt_4_5;
font = basicFont;
font.setWordSpacing(50.0);
QTest::newRow("wordSpacing") << font << QDataStream::Qt_4_5;
font = basicFont;
font.setPointSize(20);
QTest::newRow("pointSize") << font << QDataStream::Qt_1_0;
font = basicFont;
font.setPixelSize(32);
QTest::newRow("pixelSize") << font << QDataStream::Qt_3_0;
font = basicFont;
font.setStyleHint(QFont::Monospace);
QTest::newRow("styleHint") << font << QDataStream::Qt_1_0;
font = basicFont;
font.setStretch(4000);
QTest::newRow("stretch") << font << QDataStream::Qt_4_3;
font = basicFont;
font.setWeight(QFont::Light);
QTest::newRow("weight") << font << QDataStream::Qt_1_0;
font = basicFont;
font.setUnderline(true);
QTest::newRow("underline") << font << QDataStream::Qt_1_0;
font = basicFont;
font.setStrikeOut(true);
QTest::newRow("strikeOut") << font << QDataStream::Qt_1_0;
font = basicFont;
font.setFixedPitch(true);
// This fails for versions less than this, as ignorePitch is set to false
// whenever setFixedPitch() is called, but ignorePitch is considered an
// extended bit, which were apparently not available until 4.4.
QTest::newRow("fixedPitch") << font << QDataStream::Qt_4_4;
font = basicFont;
font.setLetterSpacing(QFont::AbsoluteSpacing, 10);
// Fails for 4.4 because letterSpacing wasn't read until 4.5.
QTest::newRow("letterSpacing=10") << font << QDataStream::Qt_4_5;
font = basicFont;
font.setKerning(false);
QTest::newRow("kerning") << font << QDataStream::Qt_4_0;
font = basicFont;
font.setStyleStrategy(QFont::NoFontMerging);
// This wasn't read properly until 5.4.
QTest::newRow("styleStrategy") << font << QDataStream::Qt_5_4;
font = basicFont;
font.setHintingPreference(QFont::PreferFullHinting);
// This wasn't read until 5.4.
QTest::newRow("hintingPreference") << font << QDataStream::Qt_5_4;
font = basicFont;
font.setStyleName("Regular Black Condensed");
// This wasn't read until 5.4.
QTest::newRow("styleName") << font << QDataStream::Qt_5_4;
font = basicFont;
font.setCapitalization(QFont::AllUppercase);
// This wasn't read until 5.6.
QTest::newRow("capitalization") << font << QDataStream::Qt_5_6;
}
void tst_QFont::serialize()
{
QFETCH(QFont, font);
QFETCH(QDataStream::Version, minimumStreamVersion);
QDataStream stream;
const int thisVersion = stream.version();
for (int version = minimumStreamVersion; version <= thisVersion; ++version) {
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
stream.setDevice(&buffer);
stream.setVersion(version);
stream << font;
buffer.close();
buffer.open(QIODevice::ReadOnly);
QFont readFont;
stream >> readFont;
QVERIFY2(readFont == font, qPrintable(QString::fromLatin1("Fonts do not compare equal for QDataStream version ") +
QString::fromLatin1("%1:\nactual: %2\nexpected: %3").arg(version).arg(readFont.toString()).arg(font.toString())));
}
}
void tst_QFont::deserializeQt515()
{
QFile file;
file.setFileName(QFINDTESTDATA("datastream.515"));
QVERIFY(file.open(QIODevice::ReadOnly));
QFont font;
{
QDataStream stream(&file);
stream.setVersion(QDataStream::Qt_5_15);
stream >> font;
}
QCOMPARE(font.family(), QStringLiteral("FirstFamily"));
QCOMPARE(font.families().size(), 3);
QCOMPARE(font.families().at(0), QStringLiteral("FirstFamily"));
QCOMPARE(font.families().at(1), QStringLiteral("OtherFamily1"));
QCOMPARE(font.families().at(2), QStringLiteral("OtherFamily2"));
QCOMPARE(font.pointSize(), 12);
QVERIFY(file.reset());
QByteArray fileContent = file.readAll();
QByteArray serializedContent;
{
QBuffer buffer(&serializedContent);
QVERIFY(buffer.open(QIODevice::WriteOnly));
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_15);
stream << font;
}
QCOMPARE(serializedContent, fileContent);
file.close();
}
void tst_QFont::styleName()
{
#if !defined(Q_OS_MAC)
QSKIP("Only tested on Mac");
#else
QFont font("Helvetica Neue");
font.setStyleName("UltraLight");
QCOMPARE(QFontInfo(font).styleName(), QString("UltraLight"));
#endif
}
QString getPlatformGenericFont(const char* genericName)
{
#if defined(Q_OS_UNIX) && !defined(QT_NO_FONTCONFIG) && QT_CONFIG(process)
QProcess p;
p.start(QLatin1String("fc-match"), (QStringList() << "-f%{family}" << genericName));
if (!p.waitForStarted())
qWarning("fc-match cannot be started: %s", qPrintable(p.errorString()));
if (p.waitForFinished())
return QString::fromLatin1(p.readAllStandardOutput());
#endif
return QLatin1String(genericName);
}
static inline QByteArray msgNotAcceptableFont(const QString &defaultFamily, const QStringList &acceptableFamilies)
{
QString res = QString::fromLatin1("Font family '%1' is not one of the following acceptable results: ").arg(defaultFamily);
Q_FOREACH (const QString &family, acceptableFamilies)
res += QLatin1String("\n ") + family;
return res.toLocal8Bit();
}
Q_DECLARE_METATYPE(QFont::StyleHint)
void tst_QFont::defaultFamily_data()
{
QTest::addColumn<QFont::StyleHint>("styleHint");
QTest::addColumn<QStringList>("acceptableFamilies");
QTest::newRow("serif") << QFont::Serif << (QStringList() << "Times New Roman" << "Times" << "Droid Serif" << getPlatformGenericFont("serif").split(","));
QTest::newRow("monospace") << QFont::Monospace << (QStringList() << "Courier New" << "Monaco" << "Menlo" << "Droid Sans Mono" << getPlatformGenericFont("monospace").split(","));
QTest::newRow("cursive") << QFont::Cursive << (QStringList() << "Comic Sans MS" << "Apple Chancery" << "Roboto" << "Droid Sans" << getPlatformGenericFont("cursive").split(","));
QTest::newRow("fantasy") << QFont::Fantasy << (QStringList() << "Impact" << "Zapfino" << "Roboto" << "Droid Sans" << getPlatformGenericFont("fantasy").split(","));
QTest::newRow("sans-serif") << QFont::SansSerif << (QStringList() << "Arial" << "Lucida Grande" << "Helvetica" << "Roboto" << "Droid Sans" << "Segoe UI" << getPlatformGenericFont("sans-serif").split(","));
}
void tst_QFont::defaultFamily()
{
QFETCH(QFont::StyleHint, styleHint);
QFETCH(QStringList, acceptableFamilies);
QFont f;
f.setStyleHint(styleHint);
const QString familyForHint(f.defaultFamily());
// it should at least return a family that is available.
QVERIFY(QFontDatabase::hasFamily(familyForHint));
bool isAcceptable = false;
Q_FOREACH (const QString& family, acceptableFamilies) {
if (!familyForHint.compare(family, Qt::CaseInsensitive)) {
isAcceptable = true;
break;
}
}
#if defined(Q_OS_UNIX) && defined(QT_NO_FONTCONFIG)
QSKIP("This platform does not support checking for default font acceptability");
#endif
#ifdef Q_PROCESSOR_ARM_32
if (QTestPrivate::isRunningArmOnX86())
QEXPECT_FAIL("", "Fails on ARMv7 QEMU (QTQAINFRA-4127)", Continue);
#endif
#ifdef Q_OS_ANDROID
QEXPECT_FAIL("serif", "QTBUG-69215", Continue);
#endif
QVERIFY2(isAcceptable, msgNotAcceptableFont(familyForHint, acceptableFamilies));
}
void tst_QFont::toAndFromString()
{
QFont defaultFont = QGuiApplication::font();
QString family = defaultFont.family();
const QStringList stylesList = QFontDatabase::styles(family);
if (stylesList.size() == 0)
QSKIP("Default font doesn't have any styles");
for (const QString &style : stylesList) {
QFont result;
QFont initial = QFontDatabase::font(family, style, defaultFont.pointSize());
result.fromString(initial.toString());
QCOMPARE(result, initial);
}
// Since Qt 6.0 it was changed to include more information in the description, so
// this checks for compatibility
const QString fontStringFrom515(QLatin1String("Times New Roman,18,-1,5,75,1,0,0,1,0,Regular"));
QFont fontFrom515("Times New Roman", 18);
fontFrom515.setBold(true);
fontFrom515.setItalic(true);
fontFrom515.setFixedPitch(true);
fontFrom515.setStyleName("Regular");
QFont from515String;
from515String.fromString(fontStringFrom515);
QCOMPARE(from515String, fontFrom515);
const QString fontStringFrom60(
QLatin1String("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular"));
QFont fontFrom60 = fontFrom515;
fontFrom60.setStyleStrategy(QFont::PreferBitmap);
fontFrom60.setCapitalization(QFont::AllUppercase);
fontFrom60.setLetterSpacing(QFont::PercentageSpacing, 150.5);
fontFrom60.setWordSpacing(2.5);
fontFrom60.setStretch(50);
QFont from60String;
from60String.fromString(fontStringFrom60);
QCOMPARE(fontFrom60.toString(), fontStringFrom60);
QCOMPARE(from60String, fontFrom60);
}
void tst_QFont::fromStringWithoutStyleName()
{
QFont font1;
font1.fromString("Noto Sans,12,-1,5,50,0,0,0,0,0,Regular");
QFont font2 = font1;
const QString str = "Times,16,-1,5,400,0,0,0,0,0,0,0,0,0,0,1";
font2.fromString(str);
QCOMPARE(font2.toString(), str);
const QString fontStringFrom60(
QLatin1String("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2"));
QFont font3;
font3.fromString("Noto Sans,12,-1,5,50,0,0,0,0,0,Regular");
QFont font4 = font3;
font4.fromString(fontStringFrom60);
QCOMPARE(font4.toString(), fontStringFrom60);
}
void tst_QFont::fromDegenerateString_data()
{
QTest::addColumn<QString>("string");
QTest::newRow("empty") << QString();
QTest::newRow("justAComma") << ",";
QTest::newRow("commasAndSpaces") << " , , ";
QTest::newRow("spaces") << " ";
QTest::newRow("spacesTabsAndNewlines") << " \t \n";
}
void tst_QFont::fromDegenerateString()
{
QFETCH(QString, string);
QFont f;
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Invalid description.*"));
QCOMPARE(f.fromString(string), false);
QCOMPARE(f, QFont());
}
void tst_QFont::sharing()
{
// QFontCache references the engineData
int refs_by_cache = 1;
QFont f;
f.setStyleHint(QFont::Serif);
f.exactMatch(); // loads engine
QCOMPARE(QFontPrivate::get(f)->ref.loadRelaxed(), 1);
QVERIFY(QFontPrivate::get(f)->engineData);
QCOMPARE(QFontPrivate::get(f)->engineData->ref.loadRelaxed(), 1 + refs_by_cache);
QFont f2(f);
QCOMPARE(QFontPrivate::get(f2), QFontPrivate::get(f));
QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 2);
QVERIFY(QFontPrivate::get(f2)->engineData);
QCOMPARE(QFontPrivate::get(f2)->engineData, QFontPrivate::get(f)->engineData);
QCOMPARE(QFontPrivate::get(f2)->engineData->ref.loadRelaxed(), 1 + refs_by_cache);
f2.setKerning(!f.kerning());
QVERIFY(QFontPrivate::get(f2) != QFontPrivate::get(f));
QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 1);
QVERIFY(QFontPrivate::get(f2)->engineData);
QCOMPARE(QFontPrivate::get(f2)->engineData, QFontPrivate::get(f)->engineData);
QCOMPARE(QFontPrivate::get(f2)->engineData->ref.loadRelaxed(), 2 + refs_by_cache);
f2 = f;
QCOMPARE(QFontPrivate::get(f2), QFontPrivate::get(f));
QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 2);
QVERIFY(QFontPrivate::get(f2)->engineData);
QCOMPARE(QFontPrivate::get(f2)->engineData, QFontPrivate::get(f)->engineData);
QCOMPARE(QFontPrivate::get(f2)->engineData->ref.loadRelaxed(), 1 + refs_by_cache);
if (f.pointSize() > 0)
f2.setPointSize(f.pointSize() * 2 / 3);
else
f2.setPixelSize(f.pixelSize() * 2 / 3);
QVERIFY(QFontPrivate::get(f2) != QFontPrivate::get(f));
QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 1);
QVERIFY(!QFontPrivate::get(f2)->engineData);
QVERIFY(QFontPrivate::get(f2)->engineData != QFontPrivate::get(f)->engineData);
}
void tst_QFont::familyNameWithCommaQuote_data()
{
QTest::addColumn<QString>("enteredFamilyName");
QTest::addColumn<QString>("familyName");
QTest::addColumn<QString>("chosenFamilyName");
const QString standardFont(QFont().defaultFamily());
if (standardFont.isEmpty())
QSKIP("No default font available on the system");
const QString weirdFont(QLatin1String("'My, weird'' font name',"));
const QString bogusFont(QLatin1String("BogusFont"));
const QString commaSeparated(standardFont + QLatin1String(",Times New Roman"));
const QString commaSeparatedWeird(weirdFont + QLatin1String(",") + standardFont);
const QString commaSeparatedBogus(bogusFont + QLatin1String(",") + standardFont);
QTest::newRow("standard") << standardFont << standardFont << standardFont;
QTest::newRow("weird") << weirdFont << QString("'My") << standardFont;
QTest::newRow("commaSeparated") << commaSeparated << standardFont << standardFont;
QTest::newRow("commaSeparatedWeird") << commaSeparatedWeird << QString("'My") << standardFont;
QTest::newRow("commaSeparatedBogus") << commaSeparatedBogus << bogusFont << standardFont;
}
void tst_QFont::familyNameWithCommaQuote()
{
QFETCH(QString, familyName);
QFETCH(QString, chosenFamilyName);
const int weirdFontId = QFontDatabase::addApplicationFont(":/weirdfont.otf");
QVERIFY(weirdFontId != -1);
QFont f(familyName);
QCOMPARE(f.family(), familyName);
QCOMPARE(QFontInfo(f).family(), chosenFamilyName);
QFontDatabase::removeApplicationFont(weirdFontId);
}
void tst_QFont::setFamilies_data()
{
QTest::addColumn<QStringList>("families");
QTest::addColumn<QString>("chosenFamilyName");
const QString weirdFont(QLatin1String("'My, weird'' font name',"));
const QString standardFont(QFont().defaultFamily());
if (standardFont.isEmpty())
QSKIP("No default font available on the system");
QTest::newRow("emptyFamily") << (QStringList()) << QString();
QTest::newRow("standard") << (QStringList() << standardFont) << standardFont;
QTest::newRow("weird") << (QStringList() << weirdFont) << weirdFont;
QTest::newRow("standard-weird") << (QStringList() << standardFont << weirdFont) << standardFont;
QTest::newRow("weird-standard") << (QStringList() << weirdFont << standardFont) << weirdFont;
QTest::newRow("nonexist-weird") << (QStringList() << "NonExistentFont" << weirdFont) << weirdFont;
}
void tst_QFont::setFamilies()
{
QFETCH(QStringList, families);
QFETCH(QString, chosenFamilyName);
const int weirdFontId = QFontDatabase::addApplicationFont(":/weirdfont.otf");
QVERIFY(weirdFontId != -1);
QFont f;
f.setFamilies(families);
if (!chosenFamilyName.isEmpty()) // Only check when it is not empty
QCOMPARE(QFontInfo(f).family(), chosenFamilyName);
QFontDatabase::removeApplicationFont(weirdFontId);
}
void tst_QFont::setFamiliesAndFamily_data()
{
QTest::addColumn<QStringList>("families");
QTest::addColumn<QString>("family");
QTest::addColumn<QString>("chosenFamilyName");
const QString weirdFont(QLatin1String("'My, weird'' font name',"));
const QString defaultFont(QFont().defaultFamily());
if (defaultFont.isEmpty())
QSKIP("No default font available on the system");
const QString timesFont(QLatin1String("Times"));
const QString nonExistFont(QLatin1String("NonExistentFont"));
QTest::newRow("emptyFamily") << (QStringList()) << QString() << QString();
QTest::newRow("firstInFamilies") << (QStringList() << defaultFont << timesFont) << weirdFont << defaultFont;
QTest::newRow("secondInFamilies") << (QStringList() << nonExistFont << weirdFont) << defaultFont << weirdFont;
QTest::newRow("family") << (QStringList() << nonExistFont) << defaultFont << defaultFont;
}
void tst_QFont::setFamiliesAndFamily()
{
QFETCH(QStringList, families);
QFETCH(QString, family);
QFETCH(QString, chosenFamilyName);
const int weirdFontId = QFontDatabase::addApplicationFont(":/weirdfont.otf");
QVERIFY(weirdFontId != -1);
QFont f;
f.setFamily(family);
f.setFamilies(families);
if (!family.isEmpty()) // Only check when it is not empty
QCOMPARE(QFontInfo(f).family(), chosenFamilyName);
QFontDatabase::removeApplicationFont(weirdFontId);
}
QTEST_MAIN(tst_QFont)
#include "tst_qfont.moc"

Binary file not shown.

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qfontcache Test:
#####################################################################
qt_internal_add_test(tst_qfontcache
SOURCES
tst_qfontcache.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,253 @@
// 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 <qfont.h>
#include <private/qfont_p.h>
#include <private/qfontengine_p.h>
class tst_QFontCache : public QObject
{
Q_OBJECT
public:
tst_QFontCache();
virtual ~tst_QFontCache();
private slots:
void engineData_data();
void engineData();
void engineDataFamilies_data();
void engineDataFamilies();
void threadedAccess();
void clear();
};
#ifdef QT_BUILD_INTERNAL
QT_BEGIN_NAMESPACE
// qfontdatabase.cpp
Q_AUTOTEST_EXPORT void qt_setQtEnableTestFont(bool value);
// qfontengine.cpp
Q_AUTOTEST_EXPORT void QFontEngine_startCollectingEngines();
Q_AUTOTEST_EXPORT QList<QFontEngine *> QFontEngine_stopCollectingEngines();
QT_END_NAMESPACE
#endif
tst_QFontCache::tst_QFontCache()
{
}
tst_QFontCache::~tst_QFontCache()
{
}
void tst_QFontCache::engineData_data()
{
QTest::addColumn<QString>("family");
QTest::addColumn<QStringList>("cacheKey");
QTest::newRow("unquoted-family-name") << QString("Times New Roman") << QStringList({"Times New Roman"});
QTest::newRow("quoted-family-name") << QString("'Times New Roman'") << QStringList({"Times New Roman"});
QTest::newRow("invalid") << QString("invalid") << QStringList({"invalid"});
QTest::newRow("multiple") << QString("invalid, Times New Roman")
<< QStringList({"invalid", "Times New Roman"});
QTest::newRow("multiple spaces") << QString("invalid, Times New Roman ")
<< QStringList({"invalid", "Times New Roman"});
QTest::newRow("multiple spaces quotes") << QString("'invalid', Times New Roman ")
<< QStringList({"invalid", "Times New Roman"});
QTest::newRow("multiple2") << QString("invalid, Times New Roman , foobar, 'baz'")
<< QStringList({"invalid", "Times New Roman", "foobar", "baz"});
QTest::newRow("invalid spaces") << QString("invalid spaces, Times New Roman ")
<< QStringList({"invalid spaces", "Times New Roman"});
QTest::newRow("invalid spaces quotes") << QString("'invalid spaces', 'Times New Roman' ")
<< QStringList({"invalid spaces", "Times New Roman"});
}
void tst_QFontCache::engineData()
{
QFETCH(QString, family);
QFETCH(QStringList, cacheKey);
QFont f(family);
f.exactMatch(); // loads engine
QFontPrivate *d = QFontPrivate::get(f);
QFontDef req = d->request;
// copy-pasted from QFontDatabase::load(), to engineer the cache key
if (req.pixelSize == -1) {
req.pixelSize = std::floor(((req.pointSize * d->dpi) / 72) * 100 + 0.5) / 100;
req.pixelSize = qRound(req.pixelSize);
}
if (req.pointSize < 0)
req.pointSize = req.pixelSize*72.0/d->dpi;
req.families = cacheKey;
QFontEngineData *engineData = QFontCache::instance()->findEngineData(req);
QCOMPARE(engineData, QFontPrivate::get(f)->engineData);
}
void tst_QFontCache::engineDataFamilies_data()
{
QTest::addColumn<QStringList>("families");
const QStringList multiple = { QLatin1String("invalid"), QLatin1String("Times New Roman") };
const QStringList multipleQuotes = { QLatin1String("'invalid'"), QLatin1String("Times New Roman") };
const QStringList multiple2 = { QLatin1String("invalid"), QLatin1String("Times New Roman"),
QLatin1String("foobar"), QLatin1String("'baz'") };
QTest::newRow("unquoted-family-name") << QStringList(QLatin1String("Times New Roman"));
QTest::newRow("quoted-family-name") << QStringList(QLatin1String("Times New Roman"));
QTest::newRow("invalid") << QStringList(QLatin1String("invalid"));
QTest::newRow("multiple") << multiple;
QTest::newRow("multiple spaces quotes") << multipleQuotes;
QTest::newRow("multiple2") << multiple2;
}
void tst_QFontCache::engineDataFamilies()
{
QFETCH(QStringList, families);
QFont f;
f.setFamily(QString()); // Unset the family as taken from the QGuiApplication default
f.setFamilies(families);
f.exactMatch(); // loads engine
QFontPrivate *d = QFontPrivate::get(f);
QFontDef req = d->request;
// copy-pasted from QFontDatabase::load(), to engineer the cache key
if (req.pixelSize == -1) {
req.pixelSize = std::floor(((req.pointSize * d->dpi) / 72) * 100 + 0.5) / 100;
req.pixelSize = qRound(req.pixelSize);
}
if (req.pointSize < 0)
req.pointSize = req.pixelSize*72.0/d->dpi;
req.families = families;
QFontEngineData *engineData = QFontCache::instance()->findEngineData(req);
QCOMPARE(engineData, QFontPrivate::get(f)->engineData);
}
void tst_QFontCache::clear()
{
#ifdef QT_BUILD_INTERNAL
QFontEngine_startCollectingEngines();
#else
// must not crash, at very least ;)
#endif
QFontEngine *fontEngine = 0;
#ifdef QT_BUILD_INTERNAL
{
// we're never caching the box (and the "test") font engines
// let's ensure we're not leaking them as well as the cached ones
qt_setQtEnableTestFont(true);
QFont f;
f.setFamily("__Qt__Box__Engine__");
f.exactMatch(); // loads engine
}
#endif
{
QFont f;
f.setStyleHint(QFont::Serif);
const QString familyForHint(f.defaultFamily());
// it should at least return a family that is available
QVERIFY(QFontDatabase::hasFamily(familyForHint));
f.exactMatch(); // loads engine
fontEngine = QFontPrivate::get(f)->engineForScript(QChar::Script_Common);
QVERIFY(fontEngine);
QVERIFY(QFontCache::instance()->engineCacheCount.value(fontEngine) > 0); // ensure it is cached
// acquire the engine to use it somewhere else:
// (e.g. like the we do in QFontSubset() or like QRawFont does in fromFont())
fontEngine->ref.ref();
// cache the engine once again; there is a special case when the engine is cached more than once
QFontCache::instance()->insertEngine(QFontCache::Key(QFontDef(), 0, 1), fontEngine);
}
// use it:
// e.g. fontEngine->stringToCMap(..);
// and whilst it is alive, don't hesitate to add/remove the app-local fonts:
// (QFontDatabase::{add,remove}ApplicationFont() clears the cache)
QFontCache::instance()->clear();
// release the acquired engine:
if (fontEngine) {
if (!fontEngine->ref.deref())
delete fontEngine;
fontEngine = 0;
}
// we may even exit the application now:
QFontCache::instance()->cleanup();
#ifdef QT_BUILD_INTERNAL
QList<QFontEngine *> leakedEngines = QFontEngine_stopCollectingEngines();
for (int i = 0; i < leakedEngines.size(); ++i) qWarning() << i << leakedEngines.at(i) << leakedEngines.at(i)->ref.loadRelaxed();
// and we are not leaking!
QCOMPARE(leakedEngines.size(), 0);
#endif
}
struct MessageHandler
{
MessageHandler()
{
oldMessageHandler = qInstallMessageHandler(myMessageHandler);
messages.clear();
}
~MessageHandler()
{
qInstallMessageHandler(oldMessageHandler);
}
inline static bool receivedMessage = false;
inline static QtMessageHandler oldMessageHandler = nullptr;
inline static QStringList messages;
static void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &text)
{
if (!text.startsWith("Populating font family aliases took")) {
receivedMessage = true;
messages += text;
}
if (oldMessageHandler)
oldMessageHandler(type, context, text);
}
};
void tst_QFontCache::threadedAccess()
{
MessageHandler messageHandler;
auto lambda = []{
for (const auto &family : QFontDatabase::families()) {
QFont font(family);
QFontMetrics fontMetrics(font);
fontMetrics.height();
}
};
auto *qThread = QThread::create(lambda);
qThread->start();
qThread->wait();
std::thread stdThread(lambda);
stdThread.join();
QVERIFY2(!messageHandler.receivedMessage, qPrintable(messageHandler.messages.join('\n')));
}
QTEST_MAIN(tst_QFontCache)
#include "tst_qfontcache.moc"

View File

@ -0,0 +1,4 @@
[systemFixedFont]
b2qt
# QTBUG-100948
qnx

View File

@ -0,0 +1,48 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qfontdatabase Test:
#####################################################################
# Collect test data
list(APPEND test_data "LED_REAL.TTF")
qt_internal_add_test(tst_qfontdatabase
SOURCES
tst_qfontdatabase.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
TESTDATA ${test_data}
)
# Resources:
set_source_files_properties("../../../shared/resources/testfont.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont.ttf"
)
set_source_files_properties("../../../shared/resources/testfont_condensed.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont_condensed.ttf"
)
set_source_files_properties("../../../shared/resources/testfont_italic.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont_italic.ttf"
)
set_source_files_properties("../../../shared/resources/testfont_open.otf"
PROPERTIES QT_RESOURCE_ALIAS "testfont_open.otf"
)
set(testdata_resource_files
"../../../shared/resources/testfont.ttf"
"../../../shared/resources/testfont_condensed.ttf"
"../../../shared/resources/testfont_italic.ttf"
"../../../shared/resources/testfont_open.otf"
"LED_REAL.TTF"
)
qt_internal_add_resource(tst_qfontdatabase "testdata"
PREFIX
"/"
FILES
${testdata_resource_files}
)

Binary file not shown.

View File

@ -0,0 +1,34 @@
Font: LED Real (led_real.ttf)
Created By: Matthew Welch
E-Mail: daffy-duck@worldnet.att.net
Web Address: http://home.att.net/~daffy-duck
(PGP public key available here)
LED Real, like all of my fonts, is free. You can use it for most
personal or business uses you'd like, and I ask for no money. I
would, however, like to hear from you. If you use my fonts for
something please send me a postcard or e-mail letting me know how
you used it. Send me a copy if you can or let me know where I can
find your work.
You may use this font for graphical or printed work, but you may not
sell it or include it in a collection of fonts (on CD or otherwise)
being sold. You can redistribute this font as long as you charge
nothing to receive it. If you redistribute it include this text file
with it as is (without modifications).
If you use this font for commercial purposes please credit me in
at least some little way.
About the font:
Unlike most LED/LCD style fonts mine could be recreated with an
actual LED. I created this font working from memories of the good
old Speak and Spell display. Since I don't have an actual Speak
and Spell to work from I had to just do as well as I could in its
spirit. Be warned that some characters look just like others. The
( and the <, for instance. Also C and [. Most of these will be
pretty clear in context. To see all the sections of the LED "lit
up" at once use character 127 (hold down alt and type 0127 on the
numeric keypad). This font is, of course, monospaced.

View File

@ -0,0 +1,509 @@
// 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 <qfontdatabase.h>
#include <qfontinfo.h>
#include <qfontmetrics.h>
#include <qtextlayout.h>
#include <private/qrawfont_p.h>
#include <private/qfont_p.h>
#include <private/qfontengine_p.h>
#include <qpa/qplatformfontdatabase.h>
using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcTests, "qt.text.tests")
class tst_QFontDatabase : public QObject
{
Q_OBJECT
public:
tst_QFontDatabase();
private slots:
void initTestCase();
void styles_data();
void styles();
void fixedPitch_data();
void fixedPitch();
void systemFixedFont();
#ifdef Q_OS_MAC
void trickyFonts_data();
void trickyFonts();
#endif
void widthTwoTimes_data();
void widthTwoTimes();
void addAppFont_data();
void addAppFont();
void addTwoAppFontsFromFamily();
void aliases();
void fallbackFonts();
void condensedFontWidth();
void condensedFontWidthNoFontMerging();
void condensedFontMatching();
void rasterFonts();
void smoothFonts();
void registerOpenTypePreferredNamesSystem();
void registerOpenTypePreferredNamesApplication();
void stretchRespected();
#ifdef Q_OS_WIN
void findCourier();
#endif
private:
QString m_ledFont;
QString m_testFont;
QString m_testFontCondensed;
QString m_testFontItalic;
};
tst_QFontDatabase::tst_QFontDatabase()
{
}
void tst_QFontDatabase::initTestCase()
{
m_ledFont = QFINDTESTDATA("LED_REAL.TTF");
m_testFont = QFINDTESTDATA("testfont.ttf");
m_testFontCondensed = QFINDTESTDATA("testfont_condensed.ttf");
m_testFontItalic = QFINDTESTDATA("testfont_italic.ttf");
QVERIFY(!m_ledFont.isEmpty());
QVERIFY(!m_testFont.isEmpty());
QVERIFY(!m_testFontCondensed.isEmpty());
QVERIFY(!m_testFontItalic.isEmpty());
}
void tst_QFontDatabase::styles_data()
{
QTest::addColumn<QString>("font");
QTest::newRow( "data0" ) << QString( "Times New Roman" );
}
void tst_QFontDatabase::styles()
{
QFETCH( QString, font );
QStringList styles = QFontDatabase::styles( font );
QStringList::Iterator it = styles.begin();
while ( it != styles.end() ) {
QString style = *it;
QString trimmed = style.trimmed();
++it;
QCOMPARE(style, trimmed);
}
}
void tst_QFontDatabase::fixedPitch_data()
{
QTest::addColumn<QString>("font");
QTest::addColumn<bool>("fixedPitch");
QTest::newRow( "Times New Roman" ) << QString( "Times New Roman" ) << false;
QTest::newRow( "Arial" ) << QString( "Arial" ) << false;
QTest::newRow( "Andale Mono" ) << QString( "Andale Mono" ) << true;
QTest::newRow( "Courier" ) << QString( "Courier" ) << true;
QTest::newRow( "Courier New" ) << QString( "Courier New" ) << true;
#ifndef Q_OS_MAC
QTest::newRow( "Script" ) << QString( "Script" ) << false;
QTest::newRow( "Lucida Console" ) << QString( "Lucida Console" ) << true;
QTest::newRow( "DejaVu Sans" ) << QString( "DejaVu Sans" ) << false;
QTest::newRow( "DejaVu Sans Mono" ) << QString( "DejaVu Sans Mono" ) << true;
#else
QTest::newRow( "Menlo" ) << QString( "Menlo" ) << true;
QTest::newRow( "Monaco" ) << QString( "Monaco" ) << true;
#endif
}
void tst_QFontDatabase::fixedPitch()
{
QFETCH(QString, font);
QFETCH(bool, fixedPitch);
if (!QFontDatabase::families().contains(font))
QSKIP("Font not installed");
QCOMPARE(QFontDatabase::isFixedPitch(font), fixedPitch);
QFont qfont(font);
QFontInfo fi(qfont);
QCOMPARE(fi.fixedPitch(), fixedPitch);
}
void tst_QFontDatabase::systemFixedFont() // QTBUG-54623
{
QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
QFontInfo fontInfo(font);
bool fdbSaysFixed = QFontDatabase::isFixedPitch(fontInfo.family(), fontInfo.styleName());
qCDebug(lcTests) << "system fixed font is" << font << "really fixed?" << fdbSaysFixed << fontInfo.fixedPitch();
QVERIFY(fdbSaysFixed);
QVERIFY(fontInfo.fixedPitch());
}
#ifdef Q_OS_MAC
void tst_QFontDatabase::trickyFonts_data()
{
QTest::addColumn<QString>("font");
QTest::newRow( "Geeza Pro" ) << QString( "Geeza Pro" );
}
void tst_QFontDatabase::trickyFonts()
{
QFETCH(QString, font);
if (!QFontDatabase::families().contains(font))
QSKIP( "Font not installed");
QFont qfont(font);
QFontInfo fi(qfont);
QCOMPARE(fi.family(), font);
}
#endif
void tst_QFontDatabase::widthTwoTimes_data()
{
QTest::addColumn<QString>("font");
QTest::addColumn<int>("pixelSize");
QTest::addColumn<QString>("text");
QTest::newRow("Arial") << QString("Arial") << 1000 << QString("Some text");
}
void tst_QFontDatabase::widthTwoTimes()
{
QFETCH(QString, font);
QFETCH(int, pixelSize);
QFETCH(QString, text);
QFont f;
f.setFamily(font);
f.setPixelSize(pixelSize);
QFontMetrics fm(f);
int w1 = fm.horizontalAdvance(text, 0);
int w2 = fm.horizontalAdvance(text, 0);
QCOMPARE(w1, w2);
}
void tst_QFontDatabase::addAppFont_data()
{
QTest::addColumn<bool>("useMemoryFont");
QTest::newRow("font file") << false;
QTest::newRow("memory font") << true;
}
void tst_QFontDatabase::addAppFont()
{
QFETCH(bool, useMemoryFont);
QSignalSpy fontDbChangedSpy(QGuiApplication::instance(), SIGNAL(fontDatabaseChanged()));
const QStringList oldFamilies = QFontDatabase::families();
QVERIFY(!oldFamilies.isEmpty());
fontDbChangedSpy.clear();
int id;
if (useMemoryFont) {
QFile fontfile(m_ledFont);
fontfile.open(QIODevice::ReadOnly);
QByteArray fontdata = fontfile.readAll();
QVERIFY(!fontdata.isEmpty());
id = QFontDatabase::addApplicationFontFromData(fontdata);
} else {
id = QFontDatabase::addApplicationFont(m_ledFont);
}
#if defined(Q_OS_HPUX) && defined(QT_NO_FONTCONFIG)
// Documentation says that X11 systems that don't have fontconfig
// don't support application fonts.
QCOMPARE(id, -1);
return;
#endif
QCOMPARE(fontDbChangedSpy.size(), 1);
if (id == -1)
QSKIP("Skip the test since app fonts are not supported on this system");
const QStringList addedFamilies = QFontDatabase::applicationFontFamilies(id);
QVERIFY(!addedFamilies.isEmpty());
const QStringList newFamilies = QFontDatabase::families();
QVERIFY(!newFamilies.isEmpty());
QVERIFY(newFamilies.size() >= oldFamilies.size());
for (int i = 0; i < addedFamilies.size(); ++i) {
QString family = addedFamilies.at(i);
QVERIFY(newFamilies.contains(family));
QFont qfont(family);
QFontInfo fi(qfont);
QCOMPARE(fi.family(), family);
}
QVERIFY(QFontDatabase::removeApplicationFont(id));
QCOMPARE(fontDbChangedSpy.size(), 2);
QVERIFY(QFontDatabase::families().size() <= oldFamilies.size());
}
void tst_QFontDatabase::addTwoAppFontsFromFamily()
{
int regularId = QFontDatabase::addApplicationFont(m_testFont);
if (regularId == -1)
QSKIP("Skip the test since app fonts are not supported on this system");
int italicId = QFontDatabase::addApplicationFont(m_testFontItalic);
QVERIFY(italicId != -1);
QVERIFY(!QFontDatabase::applicationFontFamilies(regularId).isEmpty());
QVERIFY(!QFontDatabase::applicationFontFamilies(italicId).isEmpty());
QString regularFontName = QFontDatabase::applicationFontFamilies(regularId).first();
QString italicFontName = QFontDatabase::applicationFontFamilies(italicId).first();
QCOMPARE(regularFontName, italicFontName);
QFont italicFont = QFontDatabase::font(italicFontName,
QString::fromLatin1("Italic"), 14);
QVERIFY(italicFont.italic());
QFontDatabase::removeApplicationFont(regularId);
QFontDatabase::removeApplicationFont(italicId);
}
void tst_QFontDatabase::aliases()
{
const QStringList families = QFontDatabase::families();
QVERIFY(!families.isEmpty());
QString firstFont;
for (int i = 0; i < families.size(); ++i) {
if (!families.at(i).contains('[')) {
firstFont = families.at(i);
break;
}
}
if (firstFont.isEmpty())
QSKIP("Skipped because there are no unambiguous font families on the system.");
QVERIFY(QFontDatabase::hasFamily(firstFont));
const QString alias = QStringLiteral("AliasToFirstFont") + firstFont;
QVERIFY(!QFontDatabase::hasFamily(alias));
QPlatformFontDatabase::registerAliasToFontFamily(firstFont, alias);
QVERIFY(QFontDatabase::hasFamily(alias));
}
void tst_QFontDatabase::fallbackFonts()
{
QTextLayout layout;
QString s;
s.append(QChar(0x31));
s.append(QChar(0x05D0));
layout.setText(s);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> runs = layout.glyphRuns(0, 1);
foreach (QGlyphRun run, runs) {
QRawFont rawFont = run.rawFont();
QVERIFY(rawFont.isValid());
QCOMPARE(run.glyphIndexes().size(), 1);
QVERIFY(run.glyphIndexes().at(0) != 0);
}
}
static QString testString()
{
return QStringLiteral("foo bar");
}
void tst_QFontDatabase::stretchRespected()
{
int italicId = QFontDatabase::addApplicationFont(m_testFontItalic);
QVERIFY(italicId != -1);
QVERIFY(!QFontDatabase::applicationFontFamilies(italicId).isEmpty());
QString italicFontName = QFontDatabase::applicationFontFamilies(italicId).first();
QFont italicFont = QFontDatabase::font(italicFontName,
QString::fromLatin1("Italic"), 14);
QVERIFY(italicFont.italic());
QFont italicStretchedFont = italicFont;
italicStretchedFont.setStretch( 400 );
QVERIFY(QFontMetricsF(italicFont).horizontalAdvance(QStringLiteral("foobar")) <
QFontMetricsF(italicStretchedFont).horizontalAdvance(QStringLiteral("foobar")));
QFontDatabase::removeApplicationFont(italicId);
}
void tst_QFontDatabase::condensedFontWidthNoFontMerging()
{
int regularFontId = QFontDatabase::addApplicationFont(m_testFont);
int condensedFontId = QFontDatabase::addApplicationFont(m_testFontCondensed);
QVERIFY(!QFontDatabase::applicationFontFamilies(regularFontId).isEmpty());
QVERIFY(!QFontDatabase::applicationFontFamilies(condensedFontId).isEmpty());
QString regularFontName = QFontDatabase::applicationFontFamilies(regularFontId).first();
QString condensedFontName = QFontDatabase::applicationFontFamilies(condensedFontId).first();
QFont condensedFont1(condensedFontName);
if (regularFontName == condensedFontName)
condensedFont1.setStyleName(QStringLiteral("Condensed"));
condensedFont1.setStyleStrategy(QFont::PreferMatch);
QFont condensedFont2 = condensedFont1;
condensedFont2.setStyleStrategy(QFont::StyleStrategy(QFont::NoFontMerging | QFont::PreferMatch));
QCOMPARE(QFontMetricsF(condensedFont2).horizontalAdvance(QStringLiteral("foobar")),
QFontMetricsF(condensedFont1).horizontalAdvance(QStringLiteral("foobar")));
}
void tst_QFontDatabase::condensedFontWidth()
{
QFontDatabase::addApplicationFont(m_testFont);
QFontDatabase::addApplicationFont(m_testFontCondensed);
QVERIFY(QFontDatabase::hasFamily("QtBidiTestFont"));
if (!QFontDatabase::hasFamily("QtBidiTestFontCondensed"))
QSKIP("This platform doesn't support font sub-family names (QTBUG-55625)");
// Test we really get a condensed font, and a not renormalized one (QTBUG-48043):
QFont testFont("QtBidiTestFont");
QFont testFontCondensed("QtBidiTestFontCondensed");
QFontMetrics fmTF(testFont);
QFontMetrics fmTFC(testFontCondensed);
QVERIFY(fmTF.horizontalAdvance(testString()) > fmTFC.horizontalAdvance(testString()));
}
void tst_QFontDatabase::condensedFontMatching()
{
QFontDatabase::removeAllApplicationFonts();
QFontDatabase::addApplicationFont(m_testFontCondensed);
if (!QFontDatabase::hasFamily("QtBidiTestFont"))
QSKIP("This platform doesn't support preferred font family names (QTBUG-53478)");
QFontDatabase::addApplicationFont(m_testFont);
// Test we correctly get the condensed font using different font matching methods:
QFont tfcByStretch("QtBidiTestFont");
tfcByStretch.setStretch(QFont::Condensed);
QFont tfcByStyleName("QtBidiTestFont");
tfcByStyleName.setStyleName("Condensed");
#ifdef Q_OS_WIN
QFont f;
f.setStyleStrategy(QFont::NoFontMerging);
QFontPrivate *font_d = QFontPrivate::get(f);
if (font_d->engineForScript(QChar::Script_Common)->type() != QFontEngine::Freetype)
QEXPECT_FAIL("","No matching of sub-family by stretch on Windows", Continue);
#endif
QCOMPARE(QFontMetrics(tfcByStretch).horizontalAdvance(testString()),
QFontMetrics(tfcByStyleName).horizontalAdvance(testString()));
if (!QFontDatabase::hasFamily("QtBidiTestFontCondensed"))
QSKIP("This platform doesn't support font sub-family names (QTBUG-55625)");
QFont tfcBySubfamilyName("QtBidiTestFontCondensed");
QCOMPARE(QFontMetrics(tfcByStyleName).horizontalAdvance(testString()),
QFontMetrics(tfcBySubfamilyName).horizontalAdvance(testString()));
}
void tst_QFontDatabase::rasterFonts()
{
QFont font(QLatin1String("Fixedsys"), 1000);
QFontInfo fontInfo(font);
if (fontInfo.family() != font.family())
QSKIP("Fixedsys font not available.");
QVERIFY(!QFontDatabase::isSmoothlyScalable(font.family()));
QVERIFY(fontInfo.pointSize() != font.pointSize());
}
void tst_QFontDatabase::smoothFonts()
{
QFont font(QLatin1String("Arial"), 1000);
QFontInfo fontInfo(font);
if (fontInfo.family() != font.family())
QSKIP("Arial font not available.");
// Smooth and bitmap scaling are mutually exclusive
QVERIFY(QFontDatabase::isSmoothlyScalable(font.family()));
QVERIFY(!QFontDatabase::isBitmapScalable(font.family()));
}
void tst_QFontDatabase::registerOpenTypePreferredNamesSystem()
{
// This font family was picked because it was the only one I had installed which showcased the
// problem
if (!QFontDatabase::hasFamily(QString::fromLatin1("Source Code Pro ExtraLight")))
QSKIP("Source Code Pro ExtraLight is not installed");
QStringList styles = QFontDatabase::styles(QString::fromLatin1("Source Code Pro"));
QVERIFY(styles.contains(QLatin1String("ExtraLight")));
}
void tst_QFontDatabase::registerOpenTypePreferredNamesApplication()
{
int id = QFontDatabase::addApplicationFont(QString::fromLatin1(":/testfont_open.otf"));
if (id == -1)
QSKIP("Skip the test since app fonts are not supported on this system");
QStringList styles = QFontDatabase::styles(QString::fromLatin1("QtBidiTestFont"));
QVERIFY(styles.contains(QLatin1String("Open")));
QFontDatabase::removeApplicationFont(id);
}
#ifdef Q_OS_WIN
void tst_QFontDatabase::findCourier()
{
QFont font = QFontDatabase::font(u"Courier"_s, u""_s, 16);
QFontInfo info(font);
QCOMPARE(info.family(), u"Courier New"_s);
QCOMPARE(info.pointSize(), 16);
font = QFontDatabase::font("Courier", "", 64);
info = font;
QCOMPARE(info.family(), u"Courier New"_s);
QCOMPARE(info.pointSize(), 64);
// By setting "PreferBitmap" we should get Courier itself.
font.setStyleStrategy(QFont::PreferBitmap);
info = font;
QCOMPARE(info.family(), u"Courier"_s);
// Which has an upper bound on point size
QCOMPARE(info.pointSize(), 19);
font.setStyleStrategy(QFont::PreferDefault);
info = font;
QCOMPARE(info.family(), u"Courier New"_s);
QCOMPARE(info.pointSize(), 64);
}
#endif
QTEST_MAIN(tst_QFontDatabase)
#include "tst_qfontdatabase.moc"

View File

@ -0,0 +1,32 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qfontmetrics Test:
#####################################################################
qt_internal_add_test(tst_qfontmetrics
SOURCES
tst_qfontmetrics.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)
# Resources:
set_source_files_properties("../../../shared/resources/testfont.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont.ttf"
)
set(testfont_resource_files
"../../../shared/resources/testfont.ttf"
"ucs4font.ttf"
)
qt_internal_add_resource(tst_qfontmetrics "testfont"
PREFIX
"/fonts"
FILES
${testfont_resource_files}
)

View File

@ -0,0 +1,392 @@
// 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 <qfont.h>
#include <qfontmetrics.h>
#include <qfontdatabase.h>
#include <private/qfontengine_p.h>
#include <qstringlist.h>
#include <qlist.h>
class tst_QFontMetrics : public QObject
{
Q_OBJECT
private slots:
void same();
void metrics();
void boundingRect();
void boundingRect2();
void elidedText_data();
void elidedText();
void veryNarrowElidedText();
void averageCharWidth();
void bypassShaping();
void elidedMultiLength();
void elidedMultiLengthF();
void inFontUcs4();
void lineWidth();
void mnemonicTextWidth();
void leadingBelowLine();
void elidedMetrics();
void zeroWidthMetrics();
void verticalMetrics_data();
void verticalMetrics();
};
void tst_QFontMetrics::same()
{
QFont font;
font.setBold(true);
QFontMetrics fm(font);
const QString text = QLatin1String("Some stupid STRING");
QCOMPARE(fm.size(0, text), fm.size(0, text)) ;
for (int i = 10; i <= 32; ++i) {
font.setPixelSize(i);
QFontMetrics fm1(font);
QCOMPARE(fm1.size(0, text), fm1.size(0, text));
}
{
QImage image;
QFontMetrics fm2(font, &image);
QString text2 = QLatin1String("Foo Foo");
QCOMPARE(fm2.size(0, text2), fm2.size(0, text2)); //used to crash
}
{
QImage image;
QFontMetricsF fm3(font, &image);
QString text2 = QLatin1String("Foo Foo");
QCOMPARE(fm3.size(0, text2), fm3.size(0, text2)); //used to crash
}
}
void tst_QFontMetrics::metrics()
{
QFont font;
// Query the QFontDatabase for a specific font, store the
// result in family, style and size.
QStringList families = QFontDatabase::families();
if (families.isEmpty())
return;
QStringList::ConstIterator f_it, f_end = families.end();
for (f_it = families.begin(); f_it != f_end; ++f_it) {
const QString &family = *f_it;
QStringList styles = QFontDatabase::styles(family);
QStringList::ConstIterator s_it, s_end = styles.end();
for (s_it = styles.begin(); s_it != s_end; ++s_it) {
const QString &style = *s_it;
if (QFontDatabase::isSmoothlyScalable(family, style)) {
// smoothly scalable font... don't need to load every pointsize
font = QFontDatabase::font(family, style, 12);
QFontMetrics fontmetrics(font);
QCOMPARE(fontmetrics.ascent() + fontmetrics.descent(),
fontmetrics.height());
QCOMPARE(fontmetrics.height() + fontmetrics.leading(),
fontmetrics.lineSpacing());
} else {
QList<int> sizes = QFontDatabase::pointSizes(family, style);
QVERIFY(!sizes.isEmpty());
QList<int>::ConstIterator z_it, z_end = sizes.end();
for (z_it = sizes.begin(); z_it != z_end; ++z_it) {
const int size = *z_it;
// Initialize the font, and check if it is an exact match
font = QFontDatabase::font(family, style, size);
QFontMetrics fontmetrics(font);
QCOMPARE(fontmetrics.ascent() + fontmetrics.descent(),
fontmetrics.height());
QCOMPARE(fontmetrics.height() + fontmetrics.leading(),
fontmetrics.lineSpacing());
}
}
}
}
}
void tst_QFontMetrics::boundingRect()
{
QFont f;
f.setPointSize(24);
QFontMetrics fm(f);
QRect r = fm.boundingRect(QChar('Y'));
QVERIFY(r.top() < 0);
r = fm.boundingRect(QString("Y"));
QVERIFY(r.top() < 0);
}
void tst_QFontMetrics::boundingRect2()
{
QFont f;
f.setPixelSize(16);
QFontMetricsF fm(f);
QString str("AVAVAVA vvvvvvvvvv fffffffff file");
QRectF br = fm.boundingRect(str);
QRectF tbr = fm.tightBoundingRect(str);
qreal advance = fm.horizontalAdvance(str);
// Bounding rect plus bearings should be similar to advance
qreal bearings = fm.leftBearing(QChar('A')) + fm.rightBearing(QChar('e'));
QVERIFY(qAbs(br.width() + bearings - advance) < fm.averageCharWidth()/2.0);
QVERIFY(qAbs(tbr.width() + bearings - advance) < fm.averageCharWidth()/2.0);
}
void tst_QFontMetrics::elidedText_data()
{
QTest::addColumn<QFont>("font");
QTest::addColumn<QString>("text");
QTest::newRow("helvetica hello") << QFont("helvetica",10) << QString("hello") ;
QTest::newRow("helvetica hello &Bye") << QFont("helvetica",10) << QString("hello&Bye") ;
}
void tst_QFontMetrics::elidedText()
{
QFETCH(QFont, font);
QFETCH(QString, text);
QFontMetrics fm(font);
int w = fm.horizontalAdvance(text);
QString newtext = fm.elidedText(text,Qt::ElideRight,w+1, 0);
QCOMPARE(text,newtext); // should not elide
newtext = fm.elidedText(text,Qt::ElideRight,w-1, 0);
QVERIFY(text != newtext); // should elide
}
void tst_QFontMetrics::veryNarrowElidedText()
{
QFont f;
QFontMetrics fm(f);
QString text("hello");
QCOMPARE(fm.elidedText(text, Qt::ElideRight, 0), QString());
}
void tst_QFontMetrics::averageCharWidth()
{
QFont f;
QFontMetrics fm(f);
QVERIFY(fm.averageCharWidth() != 0);
QFontMetricsF fmf(f);
QVERIFY(fmf.averageCharWidth() != 0);
}
void tst_QFontMetrics::bypassShaping()
{
QFont f;
f.setStyleStrategy(QFont::PreferNoShaping);
f.setKerning(false);
QFontMetricsF fm(f);
QString text = " A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z";
qreal textWidth = fm.horizontalAdvance(text);
QVERIFY(textWidth != 0);
qreal charsWidth = 0;
for (int i = 0; i < text.size(); ++i)
charsWidth += fm.horizontalAdvance(text[i]);
QCOMPARE(textWidth, charsWidth);
}
template<class FontMetrics, typename PrimitiveType> void elidedMultiLength_helper()
{
QString text1 = QLatin1String("Long Text 1\x9cShorter\x9csmall");
QString text1_long = "Long Text 1";
QString text1_short = "Shorter";
QString text1_small = "small";
FontMetrics fm = FontMetrics(QFont());
PrimitiveType width_long = fm.size(0, text1_long).width();
QCOMPARE(fm.elidedText(text1,Qt::ElideRight, 8000), text1_long);
QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_long + 1), text1_long);
QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_long - 1), text1_short);
PrimitiveType width_short = fm.size(0, text1_short).width();
QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_short + 1), text1_short);
QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_short - 1), text1_small);
// Not even wide enough for "small" - should use ellipsis
QChar ellipsisChar(0x2026);
QString text1_el = QString::fromLatin1("s") + ellipsisChar;
PrimitiveType width_small = fm.horizontalAdvance(text1_el);
QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_small + 1), text1_el);
}
void tst_QFontMetrics::elidedMultiLength()
{
elidedMultiLength_helper<QFontMetrics, int>();
}
void tst_QFontMetrics::elidedMultiLengthF()
{
elidedMultiLength_helper<QFontMetricsF, qreal>();
}
void tst_QFontMetrics::inFontUcs4()
{
int id = QFontDatabase::addApplicationFont(":/fonts/ucs4font.ttf");
QVERIFY(id >= 0);
QFont font("QtTestUcs4");
{
QFontMetrics fm(font);
QVERIFY(fm.inFontUcs4(0x1D7FF));
}
{
QFontMetricsF fm(font);
QVERIFY(fm.inFontUcs4(0x1D7FF));
}
{
QFontEngine *engine = QFontPrivate::get(font)->engineForScript(QChar::Script_Common);
QGlyphLayout glyphs;
glyphs.numGlyphs = 3;
uint buf[3];
glyphs.glyphs = buf;
QString string;
{
string.append(QChar::highSurrogate(0x1D7FF));
string.append(QChar::lowSurrogate(0x1D7FF));
glyphs.numGlyphs = 3;
glyphs.glyphs[0] = 0;
QVERIFY(engine->stringToCMap(string.constData(), string.size(),
&glyphs, &glyphs.numGlyphs,
QFontEngine::GlyphIndicesOnly));
QCOMPARE(glyphs.numGlyphs, 1);
QCOMPARE(glyphs.glyphs[0], uint(1));
}
{
string.clear();
string.append(QChar::ObjectReplacementCharacter);
glyphs.numGlyphs = 3;
glyphs.glyphs[0] = 0;
QVERIFY(engine->stringToCMap(string.constData(), string.size(),
&glyphs, &glyphs.numGlyphs,
QFontEngine::GlyphIndicesOnly));
QVERIFY(glyphs.glyphs[0] != 1);
}
}
QFontDatabase::removeApplicationFont(id);
}
void tst_QFontMetrics::lineWidth()
{
// QTBUG-13009, QTBUG-13011
QFont smallFont;
smallFont.setPointSize(8);
smallFont.setWeight(QFont::Light);
const QFontMetrics smallFontMetrics(smallFont);
QFont bigFont;
bigFont.setPointSize(40);
bigFont.setWeight(QFont::Black);
const QFontMetrics bigFontMetrics(bigFont);
QVERIFY(smallFontMetrics.lineWidth() >= 1);
QVERIFY(smallFontMetrics.lineWidth() < bigFontMetrics.lineWidth());
}
void tst_QFontMetrics::mnemonicTextWidth()
{
// QTBUG-41593
QFont f;
QFontMetrics fm(f);
const QString f1 = "File";
const QString f2 = "&File";
QCOMPARE(fm.size(Qt::TextShowMnemonic, f1), fm.size(Qt::TextShowMnemonic, f2));
QCOMPARE(fm.size(Qt::TextHideMnemonic, f1), fm.size(Qt::TextHideMnemonic, f2));
}
void tst_QFontMetrics::leadingBelowLine()
{
QScriptLine line;
line.leading = 10;
line.leadingIncluded = true;
line.ascent = 5;
QCOMPARE(line.height(), line.ascent + line.descent + line.leading);
QCOMPARE(line.base(), line.ascent);
}
void tst_QFontMetrics::elidedMetrics()
{
QString testFont = QFINDTESTDATA("fonts/testfont.ttf");
QVERIFY(!testFont.isEmpty());
int id = QFontDatabase::addApplicationFont(testFont);
QVERIFY(id >= 0);
QFont font(QFontDatabase::applicationFontFamilies(id).at(0));
font.setPixelSize(12.0);
QFontMetricsF metrics(font);
QString s = QStringLiteral("VeryLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongText");
QRectF boundingRect = metrics.boundingRect(s);
QString elided = metrics.elidedText(s, Qt::ElideRight, boundingRect.width() / 2.0);
QRectF elidedBoundingRect = metrics.boundingRect(elided);
QCOMPARE(boundingRect.height(), elidedBoundingRect.height());
QCOMPARE(boundingRect.y(), elidedBoundingRect.y());
QFontDatabase::removeApplicationFont(id);
}
void tst_QFontMetrics::zeroWidthMetrics()
{
QString zwnj(QChar(0x200c));
QString zwsp(QChar(0x200b));
QFont font;
QFontMetricsF fm(font);
QCOMPARE(fm.horizontalAdvance(zwnj), 0);
QCOMPARE(fm.horizontalAdvance(zwsp), 0);
QCOMPARE(fm.boundingRect(zwnj).width(), 0);
QCOMPARE(fm.boundingRect(zwsp).width(), 0);
QString string1 = QStringLiteral("(") + zwnj + QStringLiteral(")");
QString string2 = QStringLiteral("(") + zwnj + zwnj + QStringLiteral(")");
QString string3 = QStringLiteral("(") + zwsp + QStringLiteral(")");
QString string4 = QStringLiteral("(") + zwsp + zwsp + QStringLiteral(")");
QCOMPARE(fm.horizontalAdvance(string1), fm.horizontalAdvance(string2));
QCOMPARE(fm.horizontalAdvance(string3), fm.horizontalAdvance(string4));
QCOMPARE(fm.boundingRect(string1).width(), fm.boundingRect(string2).width());
QCOMPARE(fm.boundingRect(string3).width(), fm.boundingRect(string4).width());
QCOMPARE(fm.tightBoundingRect(string1).width(), fm.tightBoundingRect(string2).width());
QCOMPARE(fm.tightBoundingRect(string3).width(), fm.tightBoundingRect(string4).width());
}
void tst_QFontMetrics::verticalMetrics_data()
{
QTest::addColumn<QFont>("font");
QStringList families = QFontDatabase::families();
for (const QString &family : families) {
QFont font(family);
QTest::newRow(family.toUtf8()) << font;
}
}
void tst_QFontMetrics::verticalMetrics()
{
QFETCH(QFont, font);
QFontMetrics fm(font);
QVERIFY(fm.ascent() != 0 || fm.descent() != 0);
}
QTEST_MAIN(tst_QFontMetrics)
#include "tst_qfontmetrics.moc"

Binary file not shown.

View File

@ -0,0 +1,7 @@
# QTBUG-68860
[mixedScripts]
ubuntu-18.04
ubuntu-20.04
ubuntu-22.04
# QTBUG-100928
qnx

View File

@ -0,0 +1,25 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qglyphrun Test:
#####################################################################
# Resources:
set_source_files_properties("../../../shared/resources/test.ttf"
PROPERTIES QT_RESOURCE_ALIAS "test.ttf"
)
set(testdata_resource_files
"../../../shared/resources/test.ttf"
"Ligatures.otf"
)
qt_internal_add_test(tst_qglyphrun
SOURCES
tst_qglyphrun.cpp
LIBRARIES
Qt::Gui
TESTDATA ${testdata_resource_files}
BUILTIN_TESTDATA
)

Binary file not shown.

View File

@ -0,0 +1,977 @@
// 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 <qglyphrun.h>
#include <qpainter.h>
#include <qtextlayout.h>
#include <qfontdatabase.h>
// #define DEBUG_SAVE_IMAGE
class tst_QGlyphRun: public QObject
{
Q_OBJECT
#if !defined(QT_NO_RAWFONT)
private slots:
void initTestCase();
void init();
void cleanupTestCase();
void constructionAndDestruction();
void copyConstructor();
void assignment();
void equalsOperator_data();
void equalsOperator();
void isEmpty();
void textLayoutGlyphIndexes();
void drawExistingGlyphs();
void drawNonExistentGlyphs();
void drawMultiScriptText1();
void drawMultiScriptText2();
void drawRightToLeft();
void detach();
void setRawData();
void setRawDataAndGetAsVector();
void boundingRect();
void mixedScripts();
void multiLineBoundingRect();
void defaultIgnorables();
void stringIndexes();
void retrievalFlags_data();
void retrievalFlags();
void objectReplacementCharacter();
private:
int m_testFontId;
QFont m_testFont;
bool m_testFont_ok;
#endif // QT_NO_RAWFONT
};
#if !defined(QT_NO_RAWFONT)
Q_DECLARE_METATYPE(QGlyphRun);
void tst_QGlyphRun::initTestCase()
{
m_testFont_ok = false;
m_testFontId = QFontDatabase::addApplicationFont(QFINDTESTDATA("test.ttf"));
QVERIFY(m_testFontId >= 0);
m_testFont = QFont("QtsSpecialTestFont");
QCOMPARE(QFontInfo(m_testFont).family(), QString::fromLatin1("QtsSpecialTestFont"));
m_testFont_ok = true;
}
void tst_QGlyphRun::init()
{
if (!m_testFont_ok)
QSKIP("Test font is not working correctly");
}
void tst_QGlyphRun::cleanupTestCase()
{
QFontDatabase::removeApplicationFont(m_testFontId);
}
void tst_QGlyphRun::constructionAndDestruction()
{
QGlyphRun glyphIndexes;
}
static QGlyphRun make_dummy_indexes()
{
QGlyphRun glyphs;
QList<quint32> glyphIndexes;
QList<QPointF> positions;
QFont font;
font.setPointSize(18);
glyphIndexes.append(1);
glyphIndexes.append(2);
glyphIndexes.append(3);
positions.append(QPointF(1, 2));
positions.append(QPointF(3, 4));
positions.append(QPointF(5, 6));
glyphs.setRawFont(QRawFont::fromFont(font));
glyphs.setGlyphIndexes(glyphIndexes);
glyphs.setPositions(positions);
return glyphs;
}
void tst_QGlyphRun::copyConstructor()
{
QGlyphRun glyphs;
{
QList<quint32> glyphIndexes;
QList<QPointF> positions;
QFont font;
font.setPointSize(18);
glyphIndexes.append(1);
glyphIndexes.append(2);
glyphIndexes.append(3);
positions.append(QPointF(1, 2));
positions.append(QPointF(3, 4));
positions.append(QPointF(5, 6));
glyphs.setRawFont(QRawFont::fromFont(font));
glyphs.setGlyphIndexes(glyphIndexes);
glyphs.setPositions(positions);
}
QGlyphRun otherGlyphs(glyphs);
QCOMPARE(otherGlyphs.rawFont(), glyphs.rawFont());
QCOMPARE(glyphs.glyphIndexes(), otherGlyphs.glyphIndexes());
QCOMPARE(glyphs.positions(), otherGlyphs.positions());
}
void tst_QGlyphRun::assignment()
{
QGlyphRun glyphs(make_dummy_indexes());
QGlyphRun otherGlyphs = glyphs;
QCOMPARE(otherGlyphs.rawFont(), glyphs.rawFont());
QCOMPARE(glyphs.glyphIndexes(), otherGlyphs.glyphIndexes());
QCOMPARE(glyphs.positions(), otherGlyphs.positions());
}
void tst_QGlyphRun::equalsOperator_data()
{
QTest::addColumn<QGlyphRun>("one");
QTest::addColumn<QGlyphRun>("two");
QTest::addColumn<bool>("equals");
QGlyphRun one(make_dummy_indexes());
QGlyphRun two(make_dummy_indexes());
QTest::newRow("Identical") << one << two << true;
{
QGlyphRun busted(two);
QList<QPointF> positions = busted.positions();
positions[2] += QPointF(1, 1);
busted.setPositions(positions);
QTest::newRow("Different positions") << one << busted << false;
}
{
QGlyphRun busted(two);
QFont font;
font.setPixelSize(busted.rawFont().pixelSize() * 2);
busted.setRawFont(QRawFont::fromFont(font));
QTest::newRow("Different fonts") << one << busted << false;
}
{
QGlyphRun busted(two);
QList<quint32> glyphIndexes = busted.glyphIndexes();
glyphIndexes[2] += 1;
busted.setGlyphIndexes(glyphIndexes);
QTest::newRow("Different glyph indexes") << one << busted << false;
}
}
void tst_QGlyphRun::equalsOperator()
{
QFETCH(QGlyphRun, one);
QFETCH(QGlyphRun, two);
QFETCH(bool, equals);
QCOMPARE(one == two, equals);
QCOMPARE(one != two, !equals);
}
void tst_QGlyphRun::isEmpty()
{
QGlyphRun glyphs;
QVERIFY(glyphs.isEmpty());
glyphs.setGlyphIndexes(QList<quint32>() << 1 << 2 << 3);
QVERIFY(!glyphs.isEmpty());
glyphs.clear();
QVERIFY(glyphs.isEmpty());
QList<quint32> glyphIndexes = QList<quint32>() << 1 << 2 << 3;
QList<QPointF> positions = QList<QPointF>() << QPointF(0, 0) << QPointF(0, 0) << QPointF(0, 0);
glyphs.setRawData(glyphIndexes.constData(), positions.constData(), glyphIndexes.size());
QVERIFY(!glyphs.isEmpty());
}
void tst_QGlyphRun::textLayoutGlyphIndexes()
{
QString s;
s.append(QLatin1Char('A'));
s.append(QChar(0xe000));
QTextLayout layout(s);
layout.setFont(m_testFont);
layout.setCacheEnabled(true);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> listOfGlyphs = layout.glyphRuns();
QCOMPARE(listOfGlyphs.size(), 1);
QGlyphRun glyphs = listOfGlyphs.at(0);
QCOMPARE(glyphs.glyphIndexes().size(), 2);
QCOMPARE(glyphs.glyphIndexes().at(0), quint32(2));
QCOMPARE(glyphs.glyphIndexes().at(1), quint32(1));
}
void tst_QGlyphRun::drawExistingGlyphs()
{
QPixmap textLayoutDraw(1000, 1000);
QPixmap drawGlyphs(1000, 1000);
textLayoutDraw.fill(Qt::white);
drawGlyphs.fill(Qt::white);
QString s;
s.append(QLatin1Char('A'));
s.append(QChar(0xe000));
QTextLayout layout(s);
layout.setFont(m_testFont);
layout.setCacheEnabled(true);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QPainter p(&textLayoutDraw);
layout.draw(&p, QPointF(50, 50));
}
QGlyphRun glyphs = layout.glyphRuns().size() > 0
? layout.glyphRuns().at(0)
: QGlyphRun();
{
QPainter p(&drawGlyphs);
p.drawGlyphRun(QPointF(50, 50), glyphs);
}
#if defined(DEBUG_SAVE_IMAGE)
textLayoutDraw.save("drawExistingGlyphs_textLayoutDraw.png");
drawGlyphs.save("drawExistingGlyphs_drawGlyphIndexes.png");
#endif
QCOMPARE(textLayoutDraw, drawGlyphs);
}
void tst_QGlyphRun::setRawData()
{
QGlyphRun glyphRun;
glyphRun.setRawFont(QRawFont::fromFont(m_testFont));
glyphRun.setGlyphIndexes(QList<quint32>() << 2 << 2 << 2);
glyphRun.setPositions(QList<QPointF>() << QPointF(2, 3) << QPointF(20, 3) << QPointF(10, 20));
QPixmap baseline(100, 50);
baseline.fill(Qt::white);
{
QPainter p(&baseline);
p.drawGlyphRun(QPointF(3, 2), glyphRun);
}
QGlyphRun baselineCopied = glyphRun;
quint32 glyphIndexArray[3] = { 2, 2, 2 };
QPointF glyphPositionArray[3] = { QPointF(2, 3), QPointF(20, 3), QPointF(10, 20) };
glyphRun.setRawData(glyphIndexArray, glyphPositionArray, 3);
QPixmap rawDataGlyphs(100, 50);
rawDataGlyphs.fill(Qt::white);
{
QPainter p(&rawDataGlyphs);
p.drawGlyphRun(QPointF(3, 2), glyphRun);
}
quint32 otherGlyphIndexArray[1] = { 2 };
QPointF otherGlyphPositionArray[1] = { QPointF(2, 3) };
glyphRun.setRawData(otherGlyphIndexArray, otherGlyphPositionArray, 1);
QPixmap baselineCopiedPixmap(100, 50);
baselineCopiedPixmap.fill(Qt::white);
{
QPainter p(&baselineCopiedPixmap);
p.drawGlyphRun(QPointF(3, 2), baselineCopied);
}
#if defined(DEBUG_SAVE_IMAGE)
baseline.save("setRawData_baseline.png");
rawDataGlyphs.save("setRawData_rawDataGlyphs.png");
baselineCopiedPixmap.save("setRawData_baselineCopiedPixmap.png");
#endif
QCOMPARE(rawDataGlyphs, baseline);
QCOMPARE(baselineCopiedPixmap, baseline);
}
void tst_QGlyphRun::setRawDataAndGetAsVector()
{
QList<quint32> glyphIndexArray;
glyphIndexArray << 3 << 2 << 1 << 4;
QList<QPointF> glyphPositionArray;
glyphPositionArray << QPointF(1, 2) << QPointF(3, 4) << QPointF(5, 6) << QPointF(7, 8);
QGlyphRun glyphRun;
glyphRun.setRawData(glyphIndexArray.constData(), glyphPositionArray.constData(), 4);
QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
QList<QPointF> glyphPositions = glyphRun.positions();
QCOMPARE(glyphIndexes.size(), 4);
QCOMPARE(glyphPositions.size(), 4);
QCOMPARE(glyphIndexes, glyphIndexArray);
QCOMPARE(glyphPositions, glyphPositionArray);
QGlyphRun otherGlyphRun;
otherGlyphRun.setGlyphIndexes(glyphIndexArray);
otherGlyphRun.setPositions(glyphPositionArray);
QCOMPARE(glyphRun, otherGlyphRun);
}
void tst_QGlyphRun::drawNonExistentGlyphs()
{
QList<quint32> glyphIndexes;
glyphIndexes.append(4);
QList<QPointF> glyphPositions;
glyphPositions.append(QPointF(0, 0));
QGlyphRun glyphs;
glyphs.setGlyphIndexes(glyphIndexes);
glyphs.setPositions(glyphPositions);
glyphs.setRawFont(QRawFont::fromFont(m_testFont));
QPixmap image(1000, 1000);
image.fill(Qt::white);
QPixmap imageBefore = image;
{
QPainter p(&image);
p.drawGlyphRun(QPointF(50, 50), glyphs);
}
#if defined(DEBUG_SAVE_IMAGE)
image.save("drawNonExistentGlyphs.png");
#endif
QCOMPARE(image, imageBefore); // Should be unchanged
}
void tst_QGlyphRun::drawMultiScriptText1()
{
QString text;
text += QChar(0x03D0); // Greek, beta
QTextLayout textLayout(text);
textLayout.setCacheEnabled(true);
textLayout.beginLayout();
textLayout.createLine();
textLayout.endLayout();
QPixmap textLayoutDraw(1000, 1000);
textLayoutDraw.fill(Qt::white);
QPixmap drawGlyphs(1000, 1000);
drawGlyphs.fill(Qt::white);
QList<QGlyphRun> glyphsList = textLayout.glyphRuns();
QCOMPARE(glyphsList.size(), 1);
{
QPainter p(&textLayoutDraw);
textLayout.draw(&p, QPointF(50, 50));
}
{
QPainter p(&drawGlyphs);
foreach (QGlyphRun glyphs, glyphsList)
p.drawGlyphRun(QPointF(50, 50), glyphs);
}
#if defined(DEBUG_SAVE_IMAGE)
textLayoutDraw.save("drawMultiScriptText1_textLayoutDraw.png");
drawGlyphs.save("drawMultiScriptText1_drawGlyphIndexes.png");
#endif
QCOMPARE(drawGlyphs, textLayoutDraw);
}
void tst_QGlyphRun::drawMultiScriptText2()
{
QString text;
text += QChar(0x0621); // Arabic, Hamza
text += QChar(0x03D0); // Greek, beta
QTextLayout textLayout(text);
textLayout.setCacheEnabled(true);
textLayout.beginLayout();
textLayout.createLine();
textLayout.endLayout();
QPixmap textLayoutDraw(1000, 1000);
textLayoutDraw.fill(Qt::white);
QPixmap drawGlyphs(1000, 1000);
drawGlyphs.fill(Qt::white);
QList<QGlyphRun> glyphsList = textLayout.glyphRuns();
QCOMPARE(glyphsList.size(), 2);
{
QPainter p(&textLayoutDraw);
textLayout.draw(&p, QPointF(50, 50));
}
{
QPainter p(&drawGlyphs);
foreach (QGlyphRun glyphs, glyphsList)
p.drawGlyphRun(QPointF(50, 50), glyphs);
}
#if defined(DEBUG_SAVE_IMAGE)
textLayoutDraw.save("drawMultiScriptText2_textLayoutDraw.png");
drawGlyphs.save("drawMultiScriptText2_drawGlyphIndexes.png");
#endif
QCOMPARE(drawGlyphs, textLayoutDraw);
}
void tst_QGlyphRun::detach()
{
#define ARG(...) __VA_ARGS__
QGlyphRun glyphs;
glyphs.setGlyphIndexes(QList<quint32>() << 1 << 2 << 3);
QGlyphRun otherGlyphs;
otherGlyphs = glyphs;
QCOMPARE(otherGlyphs.glyphIndexes(), glyphs.glyphIndexes());
otherGlyphs.setGlyphIndexes(QList<quint32>() << 4 << 5 << 6);
QCOMPARE(otherGlyphs.glyphIndexes(), ARG({4, 5, 6}));
QCOMPARE(glyphs.glyphIndexes(), ARG({1, 2, 3}));
#undef ARG
}
void tst_QGlyphRun::drawRightToLeft()
{
QString s;
s.append(QChar(1575));
s.append(QChar(1578));
QPixmap textLayoutDraw(1000, 1000);
QPixmap drawGlyphs(1000, 1000);
textLayoutDraw.fill(Qt::white);
drawGlyphs.fill(Qt::white);
QTextLayout layout(s);
layout.setCacheEnabled(true);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QPainter p(&textLayoutDraw);
layout.draw(&p, QPointF(50, 50));
}
QGlyphRun glyphs = layout.glyphRuns().size() > 0
? layout.glyphRuns().at(0)
: QGlyphRun();
{
QPainter p(&drawGlyphs);
p.drawGlyphRun(QPointF(50, 50), glyphs);
}
#if defined(DEBUG_SAVE_IMAGE)
textLayoutDraw.save("drawRightToLeft_textLayoutDraw.png");
drawGlyphs.save("drawRightToLeft_drawGlyphIndexes.png");
#endif
QCOMPARE(textLayoutDraw, drawGlyphs);
}
void tst_QGlyphRun::boundingRect()
{
QString s(QLatin1String("AbCdE"));
QRawFont rawFont(QRawFont::fromFont(QFont()));
QVERIFY(rawFont.isValid());
QList<quint32> glyphIndexes = rawFont.glyphIndexesForString(s);
QList<QPointF> positions = rawFont.advancesForGlyphIndexes(glyphIndexes);
QCOMPARE(glyphIndexes.size(), s.size());
QCOMPARE(positions.size(), glyphIndexes.size());
QGlyphRun glyphs;
glyphs.setRawFont(rawFont);
glyphs.setGlyphIndexes(glyphIndexes);
glyphs.setPositions(positions);
QRectF boundingRect = glyphs.boundingRect();
glyphs.clear();
glyphs.setRawFont(rawFont);
glyphs.setRawData(glyphIndexes.constData(), positions.constData(), glyphIndexes.size());
QCOMPARE(glyphs.boundingRect(), boundingRect);
boundingRect = QRectF(0, 0, 1, 1);
glyphs.setBoundingRect(boundingRect);
QCOMPARE(glyphs.boundingRect(), boundingRect);
}
void tst_QGlyphRun::mixedScripts()
{
QString s;
s += QChar(0x31); // The character '1'
s += QChar(0xbc14); // Hangul character
QTextLayout layout;
layout.setFont(m_testFont);
layout.setText(s);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns();
QCOMPARE(glyphRuns.size(), 2);
}
void tst_QGlyphRun::multiLineBoundingRect()
{
QTextLayout layout;
layout.setText("Foo Bar");
layout.beginLayout();
QTextLine line = layout.createLine();
line.setNumColumns(4);
line.setPosition(QPointF(0, 0));
line = layout.createLine();
line.setPosition(QPointF(0, 10));
layout.endLayout();
QCOMPARE(layout.lineCount(), 2);
QList<QGlyphRun> firstLineGlyphRuns = layout.lineAt(0).glyphRuns();
QList<QGlyphRun> allGlyphRuns = layout.glyphRuns();
QCOMPARE(firstLineGlyphRuns.size(), 1);
QCOMPARE(allGlyphRuns.size(), 1);
QGlyphRun firstLineGlyphRun = firstLineGlyphRuns.first();
QGlyphRun allGlyphRun = allGlyphRuns.first();
QVERIFY(firstLineGlyphRun.boundingRect().height() < allGlyphRun.boundingRect().height());
}
void tst_QGlyphRun::defaultIgnorables()
{
{
QTextLayout layout;
layout.setFont(QFont("QtsSpecialTestFont"));
layout.setText(QChar(0x200D));
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> runs = layout.glyphRuns();
QCOMPARE(runs.size(), 0);
}
{
QTextLayout layout;
layout.setFont(QFont("QtsSpecialTestFont"));
layout.setText(QStringLiteral("AAA") + QChar(0xFE0F) + QStringLiteral("111"));
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> runs = layout.glyphRuns();
QVERIFY(!runs.isEmpty());
bool hasFullMainFontRun = false;
for (const QGlyphRun &run : runs) {
// QtsSpecialFont will be used for at least five characters: AA[...]111
// Depending on the font selected for the 0xFE0F variant selector, the
// third 'A' may be in QtsSpecialFont or in the fallback. This is platform-specific,
// so we accept either.
if (run.rawFont().familyName() == QStringLiteral("QtsSpecialTestFont")
&& run.glyphIndexes().size() >= 5) {
hasFullMainFontRun = true;
break;
}
}
QVERIFY(hasFullMainFontRun);
}
}
void tst_QGlyphRun::stringIndexes()
{
int ligatureFontId = QFontDatabase::addApplicationFont(QFINDTESTDATA("Ligatures.otf"));
QVERIFY(ligatureFontId >= 0);
QFont ligatureFont = QFont("QtLigatures");
QCOMPARE(QFontInfo(ligatureFont).family(), QString::fromLatin1("QtLigatures"));
QTextLayout::GlyphRunRetrievalFlags retrievalFlags
= QTextLayout::RetrieveGlyphIndexes | QTextLayout::RetrieveStringIndexes;
// Three characters -> three glyphs
{
QTextLayout layout;
layout.setText("f i");
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 2);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(2, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 2);
}
}
// Two characters -> one glyph
{
QTextLayout layout;
layout.setText("fi");
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(233));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 0);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(233));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 1);
}
}
// Four characters -> three glyphs
{
QTextLayout layout;
layout.setText("ffii");
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 3);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(71));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(233));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), uint(74));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), uint(0));
QCOMPARE(stringIndexes.at(1), uint(1));
QCOMPARE(stringIndexes.at(2), uint(3));
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, 1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(233));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), uint(1));
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, 2, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(233));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), uint(1));
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, 3, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 2);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(233));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(74));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 2);
QCOMPARE(stringIndexes.at(0), 1);
QCOMPARE(stringIndexes.at(1), 3);
}
}
// One character -> two glyphs
{
QTextLayout layout;
layout.setText(QChar(0xe6)); // LATIN SMALL LETTER AE
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 2);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(66));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(70));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 2);
QCOMPARE(stringIndexes.at(0), uint(0));
QCOMPARE(stringIndexes.at(1), uint(0));
}
// Three characters -> four glyphs
{
QTextLayout layout;
layout.setText(QString('f') + QChar(0xe6) + QChar('i'));
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 4);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(71));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(66));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), uint(70));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(3), uint(74));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 4);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 1);
QCOMPARE(stringIndexes.at(3), 2);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 3);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(66));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(70));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), uint(74));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 1);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 2);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(0, 2, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 3);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(71));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(66));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), uint(70));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 1);
}
}
// Five characters -> five glyphs
{
QTextLayout layout;
layout.setText(QLatin1String("ffi") + QChar(0xe6) + QLatin1Char('i'));
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 5);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), uint(71));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), uint(233));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), uint(66));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(3), uint(70));
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(4), uint(74));
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 5);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 3);
QCOMPARE(stringIndexes.at(3), 3);
QCOMPARE(stringIndexes.at(4), 4);
}
}
void tst_QGlyphRun::retrievalFlags_data()
{
QTest::addColumn<QTextLayout::GlyphRunRetrievalFlags>("flags");
QTest::addColumn<bool>("expectedGlyphIndexes");
QTest::addColumn<bool>("expectedStringIndexes");
QTest::addColumn<bool>("expectedString");
QTest::addColumn<bool>("expectedGlyphPositions");
QTest::newRow("Glyph indexes")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveGlyphIndexes)
<< true << false << false << false;
QTest::newRow("Glyph Positions")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveGlyphPositions)
<< false << false << false << true;
QTest::newRow("String indexes")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveStringIndexes)
<< false << true << false << false;
QTest::newRow("String")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveString)
<< false << false << true << false;
QTest::newRow("Default")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::DefaultRetrievalFlags)
<< true << false << false << true;
QTest::newRow("All")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveAll)
<< true << true << true << true;
}
void tst_QGlyphRun::retrievalFlags()
{
QFETCH(QTextLayout::GlyphRunRetrievalFlags, flags);
QFETCH(bool, expectedGlyphIndexes);
QFETCH(bool, expectedStringIndexes);
QFETCH(bool, expectedString);
QFETCH(bool, expectedGlyphPositions);
QTextLayout layout;
layout.setText(QLatin1String("abc"));
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, flags);
QVERIFY(!glyphRuns.isEmpty());
QGlyphRun firstGlyphRun = glyphRuns.first();
QCOMPARE(firstGlyphRun.glyphIndexes().isEmpty(), !expectedGlyphIndexes);
QCOMPARE(firstGlyphRun.stringIndexes().isEmpty(), !expectedStringIndexes);
QCOMPARE(firstGlyphRun.sourceString().isEmpty(), !expectedString);
QCOMPARE(firstGlyphRun.positions().isEmpty(), !expectedGlyphPositions);
}
void tst_QGlyphRun::objectReplacementCharacter()
{
QTextLayout layout;
layout.setFont(m_testFont);
layout.setText(QStringLiteral("\uFFFC"));
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns();
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.first().glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.first().glyphIndexes().first(), 5);
}
#endif // QT_NO_RAWFONT
QTEST_MAIN(tst_QGlyphRun)
#include "tst_qglyphrun.moc"

View File

@ -0,0 +1,14 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qinputcontrol Test:
#####################################################################
qt_internal_add_test(tst_qinputcontrol
SOURCES
tst_qinputcontrol.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,143 @@
// 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 <private/qinputcontrol_p.h>
#include <QtGui/QKeyEvent>
class tst_QInputControl: public QObject
{
Q_OBJECT
private slots:
void isAcceptableInput_data();
void isAcceptableInput();
void tabOnlyAcceptableInputForTextEdit();
};
void tst_QInputControl::isAcceptableInput_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<bool>("acceptable");
QTest::newRow("empty-string") << QString() << Qt::KeyboardModifiers() << false;
QTest::newRow("zwnj") << QString(QChar(0x200C)) << Qt::KeyboardModifiers() << true;
QTest::newRow("zwnj-with-ctrl") << QString(QChar(0x200C)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("zwnj-with-ctrl-shift") << QString(QChar(0x200C)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("zwj") << QString(QChar(0x200D)) << Qt::KeyboardModifiers() << true;
QTest::newRow("zwj-with-ctrl") << QString(QChar(0x200D)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("zwj-with-ctrl-shift") << QString(QChar(0x200D)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("printable-latin") << QString(QLatin1Char('a')) << Qt::KeyboardModifiers() << true;
QTest::newRow("printable-latin-with-ctrl") << QString(QLatin1Char('a')) << Qt::KeyboardModifiers(Qt::ControlModifier) << false;
QTest::newRow("printable-latin-with-ctrl-shift") << QString(QLatin1Char('a')) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << false;
QTest::newRow("printable-hebrew") << QString(QChar(0x2135)) << Qt::KeyboardModifiers() << true;
QTest::newRow("private-use-area") << QString(QChar(0xE832)) << Qt::KeyboardModifiers() << true;
QTest::newRow("good-surrogate-0") << QString::fromUtf16(u"\U0001F44D") << Qt::KeyboardModifiers() << true;
{
const QChar data[] = { QChar(0xD800), QChar(0xDC00) };
const QString str = QString(data, 2);
QTest::newRow("good-surrogate-1") << str << Qt::KeyboardModifiers() << true;
}
{
const QChar data[] = { QChar(0xD800), QChar(0xDFFF) };
const QString str = QString(data, 2);
QTest::newRow("good-surrogate-2") << str << Qt::KeyboardModifiers() << true;
}
{
const QChar data[] = { QChar(0xDBFF), QChar(0xDC00) };
const QString str = QString(data, 2);
QTest::newRow("good-surrogate-3") << str << Qt::KeyboardModifiers() << true;
}
{
const QChar data[] = { QChar(0xDBFF), QChar(0xDFFF) };
const QString str = QString(data, 2);
QTest::newRow("good-surrogate-4") << str << Qt::KeyboardModifiers() << true;
}
{
const QChar data[] = { QChar(0xD7FF), QChar(0xDC00) };
const QString str = QString(data, 2);
QTest::newRow("bad-surrogate-1") << str << Qt::KeyboardModifiers() << false;
}
{
const QChar data[] = { QChar(0xD7FF), QChar(0xDFFF) };
const QString str = QString(data, 2);
QTest::newRow("bad-surrogate-2") << str << Qt::KeyboardModifiers() << false;
}
{
const QChar data[] = { QChar(0xDC00), QChar(0xDC00) };
const QString str = QString(data, 2);
QTest::newRow("bad-surrogate-3") << str << Qt::KeyboardModifiers() << false;
}
{
const QChar data[] = { QChar(0xD800), QChar(0xE000) };
const QString str = QString(data, 2);
QTest::newRow("bad-surrogate-4") << str << Qt::KeyboardModifiers() << false;
}
{
const QChar data[] = { QChar(0xD800) };
const QString str = QString(data, 1);
QTest::newRow("bad-surrogate-5") << str << Qt::KeyboardModifiers() << false;
}
QTest::newRow("multiple-printable") << QStringLiteral("foobar") << Qt::KeyboardModifiers() << true;
QTest::newRow("rlm") << QString(QChar(0x200F)) << Qt::KeyboardModifiers() << true;
QTest::newRow("rlm-with-ctrl") << QString(QChar(0x200F)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("rlm-with-ctrl-shift") << QString(QChar(0x200F)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("lrm") << QString(QChar(0x200E)) << Qt::KeyboardModifiers() << true;
QTest::newRow("lrm-with-ctrl") << QString(QChar(0x200E)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("lrm-with-ctrl-shift") << QString(QChar(0x200E)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("rlo") << QString(QChar(0x202E)) << Qt::KeyboardModifiers() << true;
QTest::newRow("rlo-with-ctrl") << QString(QChar(0x202E)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("rlo-with-ctrl-shift") << QString(QChar(0x202E)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("lro") << QString(QChar(0x202D)) << Qt::KeyboardModifiers() << true;
QTest::newRow("lro-with-ctrl") << QString(QChar(0x202D)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("lro-with-ctrl-shift") << QString(QChar(0x202D)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("lre") << QString(QChar(0x202B)) << Qt::KeyboardModifiers() << true;
QTest::newRow("lre-with-ctrl") << QString(QChar(0x202B)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("lre-with-ctrl-shift") << QString(QChar(0x202B)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("rle") << QString(QChar(0x202A)) << Qt::KeyboardModifiers() << true;
QTest::newRow("rle-with-ctrl") << QString(QChar(0x202A)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("rle-with-ctrl-shift") << QString(QChar(0x202A)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
QTest::newRow("pdf") << QString(QChar(0x202C)) << Qt::KeyboardModifiers() << true;
QTest::newRow("pdf-with-ctrl") << QString(QChar(0x202C)) << Qt::KeyboardModifiers(Qt::ControlModifier) << true;
QTest::newRow("pdf-with-ctrl-shift") << QString(QChar(0x202C)) << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << true;
}
void tst_QInputControl::isAcceptableInput()
{
QFETCH(QString, text);
QFETCH(Qt::KeyboardModifiers, modifiers);
QFETCH(bool, acceptable);
QKeyEvent keyEvent(QKeyEvent::KeyPress, Qt::Key_unknown, modifiers, text);
{
QInputControl inputControl(QInputControl::TextEdit);
QCOMPARE(inputControl.isAcceptableInput(&keyEvent), acceptable);
}
{
QInputControl inputControl(QInputControl::LineEdit);
QCOMPARE(inputControl.isAcceptableInput(&keyEvent), acceptable);
}
}
void tst_QInputControl::tabOnlyAcceptableInputForTextEdit()
{
QKeyEvent keyEvent(QKeyEvent::KeyPress, Qt::Key_unknown, Qt::KeyboardModifiers(), QLatin1String("\t"));
{
QInputControl inputControl(QInputControl::TextEdit);
QCOMPARE(inputControl.isAcceptableInput(&keyEvent), true);
}
{
QInputControl inputControl(QInputControl::LineEdit);
QCOMPARE(inputControl.isAcceptableInput(&keyEvent), false);
}
}
QTEST_MAIN(tst_QInputControl)
#include "tst_qinputcontrol.moc"

View File

@ -0,0 +1,28 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qrawfont Test:
#####################################################################
# Resources:
set_source_files_properties("../../../shared/resources/testfont.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont.ttf"
)
set(testdata_resource_files
"../../../shared/resources/testfont.ttf"
"testfont_bold_italic.ttf"
"testfont_os2_v1.ttf"
)
qt_internal_add_test(tst_qrawfont
SOURCES
tst_qrawfont.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
TESTDATA ${testdata_resource_files}
BUILTIN_TESTDATA
)

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qstatictext Test:
#####################################################################
qt_internal_add_test(tst_qstatictext
SOURCES
tst_qstatictext.cpp
LIBRARIES
Qt::Gui
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qstatictext CONDITION QT_FEATURE_private_tests
LIBRARIES
Qt::CorePrivate
Qt::GuiPrivate
)

View File

@ -0,0 +1,882 @@
// 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 <QtCore/QSet>
#include <QtGui/QGuiApplication>
#include <QtGui/QPainter>
#include <QtGui/QImage>
#include <qstatictext.h>
#include <qpaintengine.h>
#ifdef QT_BUILD_INTERNAL
#include <private/qstatictext_p.h>
#endif
// #define DEBUG_SAVE_IMAGE
static inline QImage blankSquare()
{
// a "blank" square; we compare against in our testfunctions to verify
// that we have actually painted something
QPixmap pm(1000, 1000);
pm.fill(Qt::white);
return pm.toImage();
}
class tst_QStaticText: public QObject
{
Q_OBJECT
public:
tst_QStaticText() : m_whiteSquare(blankSquare()) {}
private slots:
void constructionAndDestruction();
void drawToPoint_data();
void drawToPoint();
void drawToRect_data();
void drawToRect();
void compareToDrawText_data();
void compareToDrawText();
void setFont();
void setTextWidth();
void prepareToCorrectData();
void prepareToWrongData();
void copyConstructor();
void translatedPainter();
void rotatedPainter();
void scaledPainter();
void projectedPainter();
#if 0
void rotatedScaledAndTranslatedPainter_data();
void rotatedScaledAndTranslatedPainter();
#endif
void transformationChanged();
void plainTextVsRichText();
void setPenPlainText_data();
void setPenPlainText();
void setPenRichText();
void richTextOverridesPen();
void unprintableCharacter_qtbug12614();
#ifdef QT_BUILD_INTERNAL
void underlinedColor_qtbug20159();
void textDocumentColor();
#endif
void multiLine();
void size_qtbug65836();
private:
bool supportsTransformations() const;
const QImage m_whiteSquare;
};
Q_DECLARE_METATYPE(QImage::Format);
void tst_QStaticText::constructionAndDestruction()
{
QStaticText text("My text");
}
void tst_QStaticText::copyConstructor()
{
QStaticText text(QLatin1String("My text"));
QTextOption textOption(Qt::AlignRight);
text.setTextOption(textOption);
text.setPerformanceHint(QStaticText::AggressiveCaching);
text.setTextWidth(123.456);
text.setTextFormat(Qt::PlainText);
QStaticText copiedText(text);
copiedText.setText(QLatin1String("Other text"));
QCOMPARE(copiedText.textOption().alignment(), Qt::AlignRight);
QCOMPARE(copiedText.performanceHint(), QStaticText::AggressiveCaching);
QCOMPARE(copiedText.textWidth(), 123.456);
QCOMPARE(copiedText.textFormat(), Qt::PlainText);
QStaticText otherCopiedText(copiedText);
otherCopiedText.setTextWidth(789);
QCOMPARE(otherCopiedText.text(), QString::fromLatin1("Other text"));
}
Q_DECLARE_METATYPE(QStaticText::PerformanceHint)
void tst_QStaticText::drawToPoint_data()
{
QTest::addColumn<QStaticText::PerformanceHint>("performanceHint");
QTest::newRow("Moderate caching") << QStaticText::ModerateCaching;
QTest::newRow("Aggressive caching") << QStaticText::AggressiveCaching;
}
void tst_QStaticText::drawToPoint()
{
QFETCH(QStaticText::PerformanceHint, performanceHint);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
text.setPerformanceHint(performanceHint);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::drawToRect_data()
{
QTest::addColumn<QStaticText::PerformanceHint>("performanceHint");
QTest::newRow("Moderate caching") << QStaticText::ModerateCaching;
QTest::newRow("Aggressive caching") << QStaticText::AggressiveCaching;
}
void tst_QStaticText::drawToRect()
{
QFETCH(QStaticText::PerformanceHint, performanceHint);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.drawText(QRectF(11, 12, 10, 500), "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextWidth(10),
p.setClipRect(QRectF(11, 12, 10, 500));
text.setPerformanceHint(performanceHint);
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12), text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("drawToRect_imageDrawText.png");
imageDrawStaticText.save("drawToRect_imageDrawStaticText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::compareToDrawText_data()
{
QTest::addColumn<QFont>("font");
QTest::newRow("default") << QFont();
QFont sansserif; sansserif.setStyleHint(QFont::SansSerif);
QFont serif; serif.setStyleHint(QFont::Serif);
QFont monospace; monospace.setStyleHint(QFont::Monospace);
QTest::newRow("sans-serif") << QFont(sansserif.defaultFamily());
QTest::newRow("serif") << QFont(serif.defaultFamily());
QTest::newRow("monospace") << QFont(monospace.defaultFamily());
}
void tst_QStaticText::compareToDrawText()
{
QFETCH(QFont, font);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.setFont(font);
p.drawText(QRectF(11, 12, 30, 500), "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticPlainText(1000, 1000);
imageDrawStaticPlainText.fill(Qt::white);
{
QPainter p(&imageDrawStaticPlainText);
p.setFont(font);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextWidth(10),
p.setClipRect(QRectF(11, 12, 30, 500));
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12), text);
}
QPixmap imageDrawStaticRichText(1000, 1000);
imageDrawStaticRichText.fill(Qt::white);
{
QPainter p(&imageDrawStaticRichText);
p.setFont(font);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextWidth(10),
p.setClipRect(QRectF(11, 12, 30, 500));
text.setTextFormat(Qt::RichText);
p.drawStaticText(QPointF(11, 12), text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("compareToDrawText_imageDrawText.png");
imageDrawStaticPlainText.save("compareToDrawText_imageDrawStaticPlainText.png");
imageDrawStaticRichText.save("compareToDrawText_imageDrawStaticRichText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticPlainText, imageDrawText);
QCOMPARE(imageDrawStaticRichText, imageDrawText);
}
void tst_QStaticText::prepareToCorrectData()
{
QTransform transform;
transform.scale(2.0, 2.0);
transform.translate(100, 10);
transform.rotate(90, Qt::ZAxis);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.setTransform(transform);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
p.setTransform(transform);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.prepare(transform, p.font());
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("prepareToCorrectData_imageDrawText.png");
imageDrawStaticText.save("prepareToCorrectData_imageDrawStaticText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
if (!supportsTransformations())
QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::prepareToWrongData()
{
QTransform transform;
transform.scale(2.0, 2.0);
transform.rotate(90, Qt::ZAxis);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.prepare(transform, p.font());
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::setFont()
{
QFont font = QGuiApplication::font();
font.setBold(true);
font.setPointSize(28);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.drawText(QRectF(0, 0, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
p.setFont(font);
p.drawText(QRectF(11, 120, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
QStaticText text;
text.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
p.drawStaticText(0, 0, text);
p.setFont(font);
p.drawStaticText(11, 120, text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("setFont_imageDrawText.png");
imageDrawStaticText.save("setFont_imageDrawStaticText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::setTextWidth()
{
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.drawText(QRectF(11, 12, 10, 500), "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextWidth(10);
p.setClipRect(QRectF(11, 12, 10, 500));
p.drawStaticText(QPointF(11, 12), text);
}
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::translatedPainter()
{
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.translate(100, 200);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
p.translate(100, 200);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
bool tst_QStaticText::supportsTransformations() const
{
QPixmap pm(10, 10);
QPainter p(&pm);
return p.paintEngine()->type() != QPaintEngine::OpenGL;
}
void tst_QStaticText::rotatedPainter()
{
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.rotate(30.0);
p.drawText(QRectF(0, 0, 1000, 100), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
QPainter p(&imageDrawStaticText);
p.rotate(30.0);
p.drawStaticText(QPoint(0, 0), text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("rotatedPainter_imageDrawText.png");
imageDrawStaticText.save("rotatedPainter_imageDrawStaticText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
if (!supportsTransformations())
QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::scaledPainter()
{
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.scale(2.0, 0.2);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
p.scale(2.0, 0.2);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
if (!supportsTransformations())
QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::projectedPainter()
{
QTransform transform;
transform.rotate(90, Qt::XAxis);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.setTransform(transform);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
p.setTransform(transform);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
QCOMPARE(imageDrawStaticText, imageDrawText);
}
#if 0
void tst_QStaticText::rotatedScaledAndTranslatedPainter_data()
{
QTest::addColumn<qreal>("offset");
for (int i=0; i<100; ++i) {
qreal offset = 300 + i / 100.;
QTest::newRow(QByteArray::number(offset).constData()) << offset;
}
}
void tst_QStaticText::rotatedScaledAndTranslatedPainter()
{
QFETCH(qreal, offset);
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.translate(offset, 0);
p.rotate(45.0);
p.scale(2.0, 2.0);
p.translate(100, 200);
p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
p.translate(offset, 0);
p.rotate(45.0);
p.scale(2.0, 2.0);
p.translate(100, 200);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("rotatedScaledAndPainter_imageDrawText.png");
imageDrawStaticText.save("rotatedScaledAndPainter_imageDrawStaticText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
if (!supportsTransformations())
QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
#endif
void tst_QStaticText::transformationChanged()
{
QPixmap imageDrawText(1000, 1000);
imageDrawText.fill(Qt::white);
{
QPainter p(&imageDrawText);
p.rotate(33.0);
p.scale(0.5, 0.7);
p.drawText(QRectF(0, 0, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
p.scale(2.0, 2.5);
p.drawText(QRectF(0, 0, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
}
QPixmap imageDrawStaticText(1000, 1000);
imageDrawStaticText.fill(Qt::white);
{
QPainter p(&imageDrawStaticText);
p.rotate(33.0);
p.scale(0.5, 0.7);
QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
text.setTextFormat(Qt::PlainText);
p.drawStaticText(QPointF(0, 0), text);
p.scale(2.0, 2.5);
p.drawStaticText(QPointF(0, 0), text);
}
#if defined(DEBUG_SAVE_IMAGE)
imageDrawText.save("transformationChanged_imageDrawText.png");
imageDrawStaticText.save("transformationChanged_imageDrawStaticText.png");
#endif
QVERIFY(imageDrawText.toImage() != m_whiteSquare);
if (!supportsTransformations())
QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort);
QCOMPARE(imageDrawStaticText, imageDrawText);
}
void tst_QStaticText::plainTextVsRichText()
{
QPixmap imagePlainText(1000, 1000);
imagePlainText.fill(Qt::white);
{
QPainter p(&imagePlainText);
QStaticText staticText;
staticText.setText("FOObar");
staticText.setTextFormat(Qt::PlainText);
p.drawStaticText(10, 10, staticText);
}
QPixmap imageRichText(1000, 1000);
imageRichText.fill(Qt::white);
{
QPainter p(&imageRichText);
QStaticText staticText;
staticText.setText("<html><body>FOObar</body></html>");
staticText.setTextFormat(Qt::RichText);
p.drawStaticText(10, 10, staticText);
}
#if defined(DEBUG_SAVE_IMAGE)
imagePlainText.save("plainTextVsRichText_imagePlainText.png");
imageRichText.save("plainTextVsRichText_imageRichText.png");
#endif
QVERIFY(imagePlainText.toImage() != m_whiteSquare);
QCOMPARE(imagePlainText, imageRichText);
}
static bool checkPixels(const QImage &image,
Qt::GlobalColor expectedColor1, Qt::GlobalColor expectedColor2,
QByteArray *errorMessage)
{
const QRgb expectedRgb1 = QColor(expectedColor1).rgba();
const QRgb expectedRgb2 = QColor(expectedColor2).rgba();
for (int x = 0, w = image.width(); x < w; ++x) {
for (int y = 0, h = image.height(); y < h; ++y) {
const QRgb pixel = image.pixel(x, y);
if (pixel != expectedRgb1 && pixel != expectedRgb2) {
QString message;
QDebug(&message) << "Color mismatch in image" << image
<< "at" << x << ',' << y << ':' << Qt::showbase << Qt::hex << pixel
<< "(expected: " << expectedRgb1 << ',' << expectedRgb2 << ')';
*errorMessage = message.toLocal8Bit();
return false;
}
}
}
return true;
}
void tst_QStaticText::setPenPlainText_data()
{
QTest::addColumn<QImage::Format>("format");
QTest::newRow("argb32pm") << QImage::Format_ARGB32_Premultiplied;
QTest::newRow("rgb32") << QImage::Format_RGB32;
QTest::newRow("rgba8888pm") << QImage::Format_RGBA8888_Premultiplied;
QTest::newRow("rgbx8888") << QImage::Format_RGBX8888;
}
void tst_QStaticText::setPenPlainText()
{
QFETCH(QImage::Format, format);
QFont font = QGuiApplication::font();
font.setStyleStrategy(QFont::NoAntialias);
QFontMetricsF fm(font);
QImage image(qCeil(fm.horizontalAdvance("XXXXX")), qCeil(fm.height()), format);
image.fill(Qt::white);
{
QPainter p(&image);
p.setFont(font);
p.setPen(Qt::yellow);
QStaticText staticText("XXXXX");
staticText.setTextFormat(Qt::PlainText);
p.drawStaticText(0, 0, staticText);
}
QByteArray errorMessage;
QVERIFY2(checkPixels(image, Qt::yellow, Qt::white, &errorMessage),
errorMessage.constData());
}
void tst_QStaticText::setPenRichText()
{
QFont font = QGuiApplication::font();
font.setStyleStrategy(QFont::NoAntialias);
QFontMetricsF fm(font);
QPixmap image(qCeil(fm.horizontalAdvance("XXXXX")), qCeil(fm.height()));
image.fill(Qt::white);
{
QPainter p(&image);
p.setFont(font);
p.setPen(Qt::green);
QStaticText staticText;
staticText.setText("<html><body>XXXXX</body></html>");
staticText.setTextFormat(Qt::RichText);
p.drawStaticText(0, 0, staticText);
}
QByteArray errorMessage;
QVERIFY2(checkPixels(image.toImage(), Qt::green, Qt::white, &errorMessage),
errorMessage.constData());
}
void tst_QStaticText::richTextOverridesPen()
{
QFont font = QGuiApplication::font();
font.setStyleStrategy(QFont::NoAntialias);
QFontMetricsF fm(font);
QPixmap image(qCeil(fm.horizontalAdvance("XXXXX")), qCeil(fm.height()));
image.fill(Qt::white);
{
QPainter p(&image);
p.setFont(font);
p.setPen(Qt::green);
QStaticText staticText;
staticText.setText("<html><body><font color=\"#ff0000\">XXXXX</font></body></html>");
staticText.setTextFormat(Qt::RichText);
p.drawStaticText(0, 0, staticText);
}
QByteArray errorMessage;
QVERIFY2(checkPixels(image.toImage(), Qt::red, Qt::white, &errorMessage),
errorMessage.constData());
}
void tst_QStaticText::unprintableCharacter_qtbug12614()
{
QString s(QChar(0x200B)); // U+200B, ZERO WIDTH SPACE
QStaticText staticText(s);
QVERIFY(staticText.size().isValid()); // Force layout. Should not crash.
}
#ifdef QT_BUILD_INTERNAL
void tst_QStaticText::underlinedColor_qtbug20159()
{
QString multiScriptText;
multiScriptText += QChar(0x0410); // Cyrillic 'A'
multiScriptText += QLatin1Char('A');
QStaticText staticText(multiScriptText);
QFont font;
font.setUnderline(true);
staticText.prepare(QTransform(), font);
QStaticTextPrivate *d = QStaticTextPrivate::get(&staticText);
QCOMPARE(d->itemCount, 2);
// The pen should not be marked as dirty when drawing the underline
QVERIFY(!d->items[0].color.isValid());
QVERIFY(!d->items[1].color.isValid());
}
void tst_QStaticText::textDocumentColor()
{
QStaticText staticText("A<font color=\"red\">B</font>");
staticText.setTextFormat(Qt::RichText);
staticText.prepare();
QStaticTextPrivate *d = QStaticTextPrivate::get(&staticText);
QCOMPARE(d->itemCount, 2);
// The pen should not be marked as dirty when drawing the underline
QVERIFY(!d->items[0].color.isValid());
QVERIFY(d->items[1].color.isValid());
QCOMPARE(d->items[1].color, QColor(Qt::red));
}
#endif
class TestPaintEngine: public QPaintEngine
{
public:
void drawTextItem(const QPointF &p, const QTextItem &) override
{
differentVerticalPositions.insert(qRound(p.y()));
}
void updateState(const QPaintEngineState &) override {}
void drawPolygon(const QPointF *, int , PolygonDrawMode ) override {}
bool begin(QPaintDevice *) override { return true; }
bool end() override { return true; }
void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) override {}
Type type() const override
{
return User;
}
QSet<int> differentVerticalPositions;
};
class TestPixmap: public QPixmap
{
public:
TestPixmap(int w, int h) : QPixmap(w, h), testPaintEngine(new TestPaintEngine) {}
~TestPixmap() { delete testPaintEngine; }
QPaintEngine *paintEngine() const override
{
return testPaintEngine;
}
TestPaintEngine *testPaintEngine;
};
void tst_QStaticText::multiLine()
{
TestPixmap pixmap(100, 100);
TestPaintEngine *paintEngine = pixmap.testPaintEngine;
{
QPainter p(&pixmap);
QStaticText text;
text.setText(QLatin1String("line 1") + QChar(QChar::LineSeparator) + QLatin1String("line 2"));
p.drawStaticText(0, 0, text);
}
QCOMPARE(paintEngine->differentVerticalPositions.size(), 2);
}
void tst_QStaticText::size_qtbug65836()
{
const QString text = QLatin1String("Lorem ipsum dolor sit amet, "
"consectetur adipiscing elit.");
QFont font("Courier");
font.setPixelSize(15);
{
QStaticText st1(text);
st1.setTextFormat(Qt::PlainText);
st1.prepare(QTransform(), font);
QStaticText st2(text);
st2.setTextFormat(Qt::RichText);
QTextOption opt;
opt.setWrapMode(QTextOption::NoWrap);
st2.setTextOption(opt);
st2.prepare(QTransform(), font);
QCOMPARE(st1.size(), st2.size());
}
{
QStaticText st1(text);
st1.setTextFormat(Qt::PlainText);
st1.setTextWidth(10.0);
st1.prepare(QTransform(), font);
QStaticText st2(text);
st2.setTextFormat(Qt::RichText);
st2.setTextWidth(10.0);
st2.prepare(QTransform(), font);
QCOMPARE(st1.size(), st2.size());
}
}
QTEST_MAIN(tst_QStaticText)
#include "tst_qstatictext.moc"

View File

@ -0,0 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qsyntaxhighlighter Test:
#####################################################################
qt_internal_add_test(tst_qsyntaxhighlighter
SOURCES
tst_qsyntaxhighlighter.cpp
LIBRARIES
Qt::Gui
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qsyntaxhighlighter CONDITION TARGET Qt::Widgets
LIBRARIES
Qt::Widgets
)

View File

@ -0,0 +1,548 @@
// 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 <QTextDocument>
#include <QTextLayout>
#include <QDebug>
#include <QAbstractTextDocumentLayout>
#include <QSyntaxHighlighter>
#include <QSignalSpy>
#ifndef QT_NO_WIDGETS
#include <QTextEdit>
#endif
class QTestDocumentLayout : public QAbstractTextDocumentLayout
{
Q_OBJECT
public:
inline QTestDocumentLayout(QTextDocument *doc)
: QAbstractTextDocumentLayout(doc), documentChangedCalled(false) {}
virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &) override {}
virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const override { return 0; }
virtual void documentChanged(int, int, int) override { documentChangedCalled = true; }
virtual int pageCount() const override { return 1; }
virtual QSizeF documentSize() const override { return QSize(); }
virtual QRectF frameBoundingRect(QTextFrame *) const override { return QRectF(); }
virtual QRectF blockBoundingRect(const QTextBlock &) const override { return QRectF(); }
bool documentChangedCalled;
};
class tst_QSyntaxHighlighter : public QObject
{
Q_OBJECT
private slots:
void init();
void cleanup();
void basic();
void basicTwo();
void removeFormatsOnDelete();
void emptyBlocks();
void setCharFormat();
void highlightOnInit();
void highlightOnInitAndAppend();
void stopHighlightingWhenStateDoesNotChange();
void unindent();
void highlightToEndOfDocument();
void highlightToEndOfDocument2();
void preservePreeditArea();
void task108530();
void avoidUnnecessaryRehighlight();
void avoidUnnecessaryDelayedRehighlight();
void noContentsChangedDuringHighlight();
void rehighlight();
void rehighlightBlock();
#ifndef QT_NO_WIDGETS
void textEditParent();
#endif
private:
QTextDocument *doc;
QTestDocumentLayout *lout;
QTextCursor cursor;
};
void tst_QSyntaxHighlighter::init()
{
doc = new QTextDocument;
lout = new QTestDocumentLayout(doc);
doc->setDocumentLayout(lout);
cursor = QTextCursor(doc);
}
void tst_QSyntaxHighlighter::cleanup()
{
delete doc;
doc = 0;
}
class TestHighlighter : public QSyntaxHighlighter
{
public:
inline TestHighlighter(const QList<QTextLayout::FormatRange> &fmts, QTextDocument *parent)
: QSyntaxHighlighter(parent), formats(fmts), highlighted(false), callCount(0)
{
}
inline TestHighlighter(QObject *parent)
: QSyntaxHighlighter(parent) {}
inline TestHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent), highlighted(false), callCount(0) {}
virtual void highlightBlock(const QString &text) override
{
for (int i = 0; i < formats.size(); ++i) {
const QTextLayout::FormatRange &range = formats.at(i);
setFormat(range.start, range.length, range.format);
}
highlighted = true;
highlightedText += text;
++callCount;
}
QList<QTextLayout::FormatRange> formats;
bool highlighted;
int callCount;
QString highlightedText;
};
void tst_QSyntaxHighlighter::basic()
{
QList<QTextLayout::FormatRange> formats;
QTextLayout::FormatRange range;
range.start = 0;
range.length = 2;
range.format.setForeground(Qt::blue);
formats.append(range);
range.start = 4;
range.length = 2;
range.format.setFontItalic(true);
formats.append(range);
range.start = 9;
range.length = 2;
range.format.setFontUnderline(true);
formats.append(range);
TestHighlighter *hl = new TestHighlighter(formats, doc);
lout->documentChangedCalled = false;
doc->setPlainText("Hello World");
QVERIFY(hl->highlighted);
QVERIFY(lout->documentChangedCalled);
QCOMPARE(doc->begin().layout()->formats(), formats);
}
class CommentTestHighlighter : public QSyntaxHighlighter
{
public:
inline CommentTestHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent), highlighted(false) {}
inline void reset()
{
highlighted = false;
}
virtual void highlightBlock(const QString &text) override
{
QTextCharFormat commentFormat;
commentFormat.setForeground(Qt::darkGreen);
commentFormat.setFontWeight(QFont::StyleItalic);
commentFormat.setFontFixedPitch(true);
int textLength = text.size();
if (text.startsWith(QLatin1Char(';'))){
// The entire line is a comment
setFormat(0, textLength, commentFormat);
highlighted = true;
}
}
bool highlighted;
};
void tst_QSyntaxHighlighter::basicTwo()
{
// Done for task 104409
CommentTestHighlighter *hl = new CommentTestHighlighter(doc);
doc->setPlainText("; a test");
QVERIFY(hl->highlighted);
QVERIFY(lout->documentChangedCalled);
}
void tst_QSyntaxHighlighter::removeFormatsOnDelete()
{
QList<QTextLayout::FormatRange> formats;
QTextLayout::FormatRange range;
range.start = 0;
range.length = 9;
range.format.setForeground(Qt::blue);
formats.append(range);
TestHighlighter *hl = new TestHighlighter(formats, doc);
lout->documentChangedCalled = false;
doc->setPlainText("Hello World");
QVERIFY(hl->highlighted);
QVERIFY(lout->documentChangedCalled);
lout->documentChangedCalled = false;
QVERIFY(!doc->begin().layout()->formats().isEmpty());
delete hl;
QVERIFY(doc->begin().layout()->formats().isEmpty());
QVERIFY(lout->documentChangedCalled);
}
void tst_QSyntaxHighlighter::emptyBlocks()
{
TestHighlighter *hl = new TestHighlighter(doc);
cursor.insertText("Foo");
cursor.insertBlock();
cursor.insertBlock();
hl->highlighted = false;
cursor.insertBlock();
QVERIFY(hl->highlighted);
}
void tst_QSyntaxHighlighter::setCharFormat()
{
TestHighlighter *hl = new TestHighlighter(doc);
cursor.insertText("FooBar");
cursor.insertBlock();
cursor.insertText("Blah");
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
QTextCharFormat fmt;
fmt.setFontItalic(true);
hl->highlighted = false;
hl->callCount = 0;
cursor.mergeCharFormat(fmt);
QVERIFY(hl->highlighted);
QCOMPARE(hl->callCount, 2);
}
void tst_QSyntaxHighlighter::highlightOnInit()
{
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertText("World");
TestHighlighter *hl = new TestHighlighter(doc);
QTRY_VERIFY(hl->highlighted);
}
void tst_QSyntaxHighlighter::highlightOnInitAndAppend()
{
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertText("World");
TestHighlighter *hl = new TestHighlighter(doc);
cursor.insertBlock();
cursor.insertText("More text");
QTRY_VERIFY(hl->highlighted);
QVERIFY(hl->highlightedText.endsWith(doc->toPlainText().remove(QLatin1Char('\n'))));
}
class StateTestHighlighter : public QSyntaxHighlighter
{
public:
inline StateTestHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent), state(0), highlighted(false) {}
inline void reset()
{
highlighted = false;
state = 0;
}
virtual void highlightBlock(const QString &text) override
{
highlighted = true;
if (text == QLatin1String("changestate"))
setCurrentBlockState(state++);
}
int state;
bool highlighted;
};
void tst_QSyntaxHighlighter::stopHighlightingWhenStateDoesNotChange()
{
cursor.insertText("state");
cursor.insertBlock();
cursor.insertText("changestate");
cursor.insertBlock();
cursor.insertText("keepstate");
cursor.insertBlock();
cursor.insertText("changestate");
cursor.insertBlock();
cursor.insertText("changestate");
StateTestHighlighter *hl = new StateTestHighlighter(doc);
QTRY_VERIFY(hl->highlighted);
hl->reset();
// turn the text of the first block into 'changestate'
cursor.movePosition(QTextCursor::Start);
cursor.insertText("change");
// verify that we highlighted only to the 'keepstate' block,
// not beyond
QCOMPARE(hl->state, 2);
}
void tst_QSyntaxHighlighter::unindent()
{
const QString spaces(" ");
const QString text("Foobar");
QString plainText;
for (int i = 0; i < 5; ++i) {
cursor.insertText(spaces + text);
cursor.insertBlock();
plainText += spaces;
plainText += text;
plainText += QLatin1Char('\n');
}
QCOMPARE(doc->toPlainText(), plainText);
TestHighlighter *hl = new TestHighlighter(doc);
QTRY_VERIFY(hl->highlighted);
hl->callCount = 0;
cursor.movePosition(QTextCursor::Start);
cursor.beginEditBlock();
plainText.clear();
for (int i = 0; i < 5; ++i) {
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
cursor.removeSelectedText();
cursor.movePosition(QTextCursor::NextBlock);
plainText += text;
plainText += QLatin1Char('\n');
}
cursor.endEditBlock();
QCOMPARE(doc->toPlainText(), plainText);
QCOMPARE(hl->callCount, 5);
}
void tst_QSyntaxHighlighter::highlightToEndOfDocument()
{
TestHighlighter *hl = new TestHighlighter(doc);
hl->callCount = 0;
cursor.movePosition(QTextCursor::Start);
cursor.beginEditBlock();
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertBlock();
cursor.insertText("World");
cursor.insertBlock();
cursor.endEditBlock();
QCOMPARE(hl->callCount, 4);
}
void tst_QSyntaxHighlighter::highlightToEndOfDocument2()
{
TestHighlighter *hl = new TestHighlighter(doc);
hl->callCount = 0;
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignLeft);
cursor.setBlockFormat(fmt);
cursor.insertText("Three\nLines\nHere");
cursor.endEditBlock();
QCOMPARE(hl->callCount, 3);
}
void tst_QSyntaxHighlighter::preservePreeditArea()
{
QList<QTextLayout::FormatRange> formats;
QTextLayout::FormatRange range;
range.start = 0;
range.length = 8;
range.format.setForeground(Qt::blue);
formats << range;
range.start = 9;
range.length = 1;
range.format.setForeground(Qt::red);
formats << range;
TestHighlighter *hl = new TestHighlighter(formats, doc);
doc->setPlainText("Hello World");
cursor.movePosition(QTextCursor::Start);
QTextLayout *layout = cursor.block().layout();
layout->setPreeditArea(5, QString("foo"));
range.start = 5;
range.length = 3;
range.format.setFontUnderline(true);
formats.clear();
formats << range;
hl->callCount = 0;
cursor.beginEditBlock();
layout->setFormats(formats);
cursor.endEditBlock();
QCOMPARE(hl->callCount, 1);
formats = layout->formats();
QCOMPARE(formats.size(), 3);
range = formats.at(0);
QCOMPARE(range.start, 5);
QCOMPARE(range.length, 3);
QVERIFY(range.format.fontUnderline());
range = formats.at(1);
QCOMPARE(range.start, 0);
QCOMPARE(range.length, 8 + 3);
range = formats.at(2);
QCOMPARE(range.start, 9 + 3);
QCOMPARE(range.length, 1);
}
void tst_QSyntaxHighlighter::task108530()
{
TestHighlighter *hl = new TestHighlighter(doc);
cursor.insertText("test");
hl->callCount = 0;
hl->highlightedText.clear();
cursor.movePosition(QTextCursor::Start);
cursor.insertBlock();
QCOMPARE(hl->highlightedText, QString("test"));
QCOMPARE(hl->callCount, 2);
}
void tst_QSyntaxHighlighter::avoidUnnecessaryRehighlight()
{
TestHighlighter *hl = new TestHighlighter(doc);
QVERIFY(!hl->highlighted);
doc->setPlainText("Hello World");
QVERIFY(hl->highlighted);
hl->highlighted = false;
QCoreApplication::processEvents();
QVERIFY(!hl->highlighted);
}
void tst_QSyntaxHighlighter::avoidUnnecessaryDelayedRehighlight()
{
// Having text in the document before creating the highlighter starts the delayed rehighlight
cursor.insertText("Hello World");
TestHighlighter *hl = new TestHighlighter(doc);
QVERIFY(!hl->highlighted);
hl->rehighlight();
QVERIFY(hl->highlighted);
hl->highlighted = false;
// Process events, including delayed rehighlight emission
QCoreApplication::processEvents();
// Should be cancelled and no extra rehighlight should be done
QVERIFY(!hl->highlighted);
}
void tst_QSyntaxHighlighter::noContentsChangedDuringHighlight()
{
QList<QTextLayout::FormatRange> formats;
QTextLayout::FormatRange range;
range.start = 0;
range.length = 10;
range.format.setForeground(Qt::blue);
formats.append(range);
TestHighlighter *hl = new TestHighlighter(formats, doc);
lout->documentChangedCalled = false;
QTextCursor cursor(doc);
QSignalSpy contentsChangedSpy(doc, SIGNAL(contentsChanged()));
cursor.insertText("Hello World");
QCOMPARE(contentsChangedSpy.size(), 1);
QVERIFY(hl->highlighted);
QVERIFY(lout->documentChangedCalled);
}
void tst_QSyntaxHighlighter::rehighlight()
{
TestHighlighter *hl = new TestHighlighter(doc);
hl->callCount = 0;
doc->setPlainText("Hello");
hl->callCount = 0;
hl->rehighlight();
QCOMPARE(hl->callCount, 1);
}
void tst_QSyntaxHighlighter::rehighlightBlock()
{
TestHighlighter *hl = new TestHighlighter(doc);
cursor.movePosition(QTextCursor::Start);
cursor.beginEditBlock();
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertText("World");
cursor.endEditBlock();
hl->callCount = 0;
hl->highlightedText.clear();
QTextBlock block = doc->begin();
hl->rehighlightBlock(block);
QCOMPARE(hl->highlightedText, QString("Hello"));
QCOMPARE(hl->callCount, 1);
hl->callCount = 0;
hl->highlightedText.clear();
hl->rehighlightBlock(block.next());
QCOMPARE(hl->highlightedText, QString("World"));
QCOMPARE(hl->callCount, 1);
}
#ifndef QT_NO_WIDGETS
void tst_QSyntaxHighlighter::textEditParent()
{
QTextEdit textEdit;
TestHighlighter *hl = new TestHighlighter(&textEdit);
QCOMPARE(hl->document(), textEdit.document());
}
#endif
QTEST_MAIN(tst_QSyntaxHighlighter)
#include "tst_qsyntaxhighlighter.moc"

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextblock Test:
#####################################################################
qt_internal_add_test(tst_qtextblock
SOURCES
tst_qtextblock.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,142 @@
// 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 <QtTest/QTest>
#include <qtextdocument.h>
#include <qdebug.h>
#ifndef Q_OS_WIN
# include <private/qtextdocument_p.h>
#endif
#include <qtextobject.h>
#include <qtextcursor.h>
QT_FORWARD_DECLARE_CLASS(QTextDocument)
class tst_QTextBlock : public QObject
{
Q_OBJECT
private slots:
void init();
void cleanup();
void fragmentOverBlockBoundaries();
void excludeParagraphSeparatorFragment();
void backwardsBlockIterator();
void previousBlock_qtbug18026();
void removedBlock_qtbug18500();
private:
QTextDocument *doc;
QTextCursor cursor;
};
void tst_QTextBlock::init()
{
doc = new QTextDocument;
cursor = QTextCursor(doc);
}
void tst_QTextBlock::cleanup()
{
cursor = QTextCursor();
delete doc;
doc = 0;
}
void tst_QTextBlock::fragmentOverBlockBoundaries()
{
/* this creates two fragments in the piecetable:
* 1) 'hello<parag separator here>world'
* 2) '<parag separator>'
* (they are not united because the former was interested after the latter,
* hence their position in the pt buffer is the other way around)
*/
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertText("World");
cursor.movePosition(QTextCursor::Start);
const QTextDocument *doc = cursor.block().document();
QVERIFY(doc);
// Block separators are always a fragment of their self. Thus:
// |Hello|\b|World|\b|
#if !defined(Q_OS_WIN)
QCOMPARE(QTextDocumentPrivate::get(doc)->fragmentMap().numNodes(), 4);
#endif
QCOMPARE(cursor.block().text(), QString("Hello"));
cursor.movePosition(QTextCursor::NextBlock);
QCOMPARE(cursor.block().text(), QString("World"));
}
void tst_QTextBlock::excludeParagraphSeparatorFragment()
{
QTextCharFormat fmt;
fmt.setForeground(Qt::blue);
cursor.insertText("Hello", fmt);
QTextBlock block = doc->begin();
QVERIFY(block.isValid());
QTextBlock::Iterator it = block.begin();
QTextFragment fragment = it.fragment();
QVERIFY(fragment.isValid());
QCOMPARE(fragment.text(), QString("Hello"));
++it;
QVERIFY(it.atEnd());
QCOMPARE(it, block.end());
}
void tst_QTextBlock::backwardsBlockIterator()
{
QTextCharFormat fmt;
fmt.setForeground(Qt::magenta);
cursor.insertText("A", fmt);
fmt.setForeground(Qt::red);
cursor.insertText("A", fmt);
fmt.setForeground(Qt::magenta);
cursor.insertText("A", fmt);
QTextBlock block = doc->begin();
QVERIFY(block.isValid());
QTextBlock::Iterator it = block.begin();
QCOMPARE(it.fragment().position(), 0);
++it;
QCOMPARE(it.fragment().position(), 1);
++it;
QCOMPARE(it.fragment().position(), 2);
--it;
QCOMPARE(it.fragment().position(), 1);
--it;
QCOMPARE(it.fragment().position(), 0);
}
void tst_QTextBlock::previousBlock_qtbug18026()
{
QTextBlock last = doc->end().previous();
QVERIFY(last.isValid());
}
void tst_QTextBlock::removedBlock_qtbug18500()
{
cursor.insertText("line 1\nline 2\nline 3 \nline 4\n");
cursor.setPosition(7);
QTextBlock block = cursor.block();
cursor.setPosition(21, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
QVERIFY(!block.isValid());
}
QTEST_MAIN(tst_QTextBlock)
#include "tst_qtextblock.moc"

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextcursor Test:
#####################################################################
qt_internal_add_test(tst_qtextcursor
SOURCES
tst_qtextcursor.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextdocument Test:
#####################################################################
qt_internal_add_test(tst_qtextdocument
SOURCES
common.h
tst_qtextdocument.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
Qt::Xml
)

View File

@ -0,0 +1,55 @@
// 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 <QAbstractTextDocumentLayout>
#include <private/qtextdocument_p.h>
#ifndef COMMON_H
#define COMMON_H
class QTestDocumentLayout : public QAbstractTextDocumentLayout
{
Q_OBJECT
public:
QTestDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc), f(-1), called(false) {}
virtual void draw(QPainter *, const PaintContext &) override {}
virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const override { return 0; }
virtual void documentChanged(int from, int oldLength, int length) override
{
called = true;
lastDocumentLengths.append(QTextDocumentPrivate::get(document())->length());
if (f < 0)
return;
if(from != f ||
o != oldLength ||
l != length) {
qDebug("checkDocumentChanged: got %d %d %d, expected %d %d %d", from, oldLength, length, f, o, l);
error = true;
}
}
virtual int pageCount() const override { return 1; }
virtual QSizeF documentSize() const override { return QSizeF(); }
virtual QRectF frameBoundingRect(QTextFrame *) const override { return QRectF(); }
virtual QRectF blockBoundingRect(const QTextBlock &) const override { return QRectF(); }
int f;
int o;
int l;
void expect(int from, int oldLength, int length) {
f = from;
o = oldLength;
l = length;
error = false;
called = false;
}
bool error;
bool called;
QList<int> lastDocumentLengths;
};
#endif

View File

@ -0,0 +1,3 @@
body {
font: normal 400 14px/1.2 Arial;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextdocumentfragment Test:
#####################################################################
qt_internal_add_test(tst_qtextdocumentfragment
SOURCES
tst_qtextdocumentfragment.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
[imageAtRightAlignedTab]
rhel-6.6
rhel-7.4
rhel-7.6
sles
centos

View File

@ -0,0 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextdocumentlayout Test:
#####################################################################
qt_internal_add_test(tst_qtextdocumentlayout
SOURCES
tst_qtextdocumentlayout.cpp
LIBRARIES
Qt::Gui
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qtextdocumentlayout CONDITION TARGET Qt::Widgets
LIBRARIES
Qt::Widgets
)

View File

@ -0,0 +1,421 @@
// 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 <qtextdocument.h>
#include <qabstracttextdocumentlayout.h>
#include <qdebug.h>
#include <qpainter.h>
#include <qtexttable.h>
#ifndef QT_NO_WIDGETS
#include <qtextedit.h>
#include <qscrollbar.h>
#endif
class tst_QTextDocumentLayout : public QObject
{
Q_OBJECT
private slots:
void init();
void cleanup();
void cleanupTestCase();
void defaultPageSizeHandling();
void idealWidth();
void lineSeparatorFollowingTable();
#ifndef QT_NO_WIDGETS
void wrapAtWordBoundaryOrAnywhere();
#endif
void inlineImage();
void clippedTableCell();
void floatingTablePageBreak();
void imageAtRightAlignedTab();
void blockVisibility();
void testHitTest();
void largeImage();
private:
QTextDocument *doc;
};
void tst_QTextDocumentLayout::init()
{
doc = new QTextDocument;
}
void tst_QTextDocumentLayout::cleanup()
{
delete doc;
doc = 0;
}
void tst_QTextDocumentLayout::cleanupTestCase()
{
if (qgetenv("QTEST_KEEP_IMAGEDATA").toInt() == 0) {
QFile::remove(QLatin1String("expected.png"));
QFile::remove(QLatin1String("img.png"));
}
}
void tst_QTextDocumentLayout::defaultPageSizeHandling()
{
QAbstractTextDocumentLayout *layout = doc->documentLayout();
QVERIFY(layout);
QVERIFY(!doc->pageSize().isValid());
QSizeF docSize = layout->documentSize();
QVERIFY(docSize.width() > 0 && docSize.width() < 1000);
QVERIFY(docSize.height() > 0 && docSize.height() < 1000);
doc->setPlainText("Some text\nwith a few lines\nand not real information\nor anything otherwise useful");
docSize = layout->documentSize();
QVERIFY(docSize.isValid());
QVERIFY(docSize.width() != INT_MAX);
QVERIFY(docSize.height() != INT_MAX);
}
void tst_QTextDocumentLayout::idealWidth()
{
doc->setPlainText("Some text\nwith a few lines\nand not real information\nor anything otherwise useful");
doc->setTextWidth(1000);
QCOMPARE(doc->textWidth(), qreal(1000));
QCOMPARE(doc->size().width(), doc->textWidth());
QVERIFY(doc->idealWidth() < doc->textWidth());
QVERIFY(doc->idealWidth() > 0);
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute);
QTextCursor cursor(doc);
cursor.select(QTextCursor::Document);
cursor.mergeBlockFormat(fmt);
QCOMPARE(doc->textWidth(), qreal(1000));
QCOMPARE(doc->size().width(), doc->textWidth());
QVERIFY(doc->idealWidth() < doc->textWidth());
QVERIFY(doc->idealWidth() > 0);
}
// none of the QTextLine items in the document should intersect with the margin rect
void tst_QTextDocumentLayout::lineSeparatorFollowingTable()
{
QString html_begin("<html><table border=1><tr><th>Column 1</th></tr><tr><td>Data</td></tr></table><br>");
QString html_text("bla bla bla bla bla bla bla bla<br>");
QString html_end("<table border=1><tr><th>Column 1</th></tr><tr><td>Data</td></tr></table></html>");
QString html = html_begin;
for (int i = 0; i < 80; ++i)
html += html_text;
html += html_end;
doc->setHtml(html);
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::Start);
const int margin = 87;
const int pageWidth = 873;
const int pageHeight = 1358;
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
fmt.setMargin(margin);
doc->rootFrame()->setFrameFormat(fmt);
QFont font(doc->defaultFont());
font.setPointSize(10);
doc->setDefaultFont(font);
doc->setPageSize(QSizeF(pageWidth, pageHeight));
QRectF marginRect(QPointF(0, pageHeight - margin), QSizeF(pageWidth, 2 * margin));
// force layouting
doc->pageCount();
for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
QTextLayout *layout = block.layout();
for (int i = 0; i < layout->lineCount(); ++i) {
QTextLine line = layout->lineAt(i);
QRectF rect = line.rect().translated(layout->position());
QVERIFY(!rect.intersects(marginRect));
}
}
}
#ifndef QT_NO_WIDGETS
void tst_QTextDocumentLayout::wrapAtWordBoundaryOrAnywhere()
{
//task 150562
QTextEdit edit;
edit.setText("<table><tr><td>hello hello hello"
"thisisabigwordthisisabigwordthisisabigwordthisisabigwordthisisabigword"
"hello hello hello</td></tr></table>");
edit.setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
edit.resize(100, 100);
edit.show();
QVERIFY(!edit.horizontalScrollBar()->isVisible());
}
#endif
void tst_QTextDocumentLayout::inlineImage()
{
doc->setPageSize(QSizeF(800, 500));
QImage img(400, 400, QImage::Format_RGB32);
QLatin1String name("bigImage");
doc->addResource(QTextDocument::ImageResource, QUrl(name), img);
QTextImageFormat imgFormat;
imgFormat.setName(name);
imgFormat.setWidth(img.width());
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
qreal height = doc->pageSize().height() - fmt.topMargin() - fmt.bottomMargin();
imgFormat.setHeight(height);
QTextCursor cursor(doc);
cursor.insertImage(imgFormat);
QCOMPARE(doc->pageCount(), 1);
}
void tst_QTextDocumentLayout::clippedTableCell()
{
const char *html =
"<table style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\""
"border=\"0\" margin=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td></td></tr></table>";
doc->setHtml(html);
doc->pageSize();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::Right);
QTextTable *table = cursor.currentTable();
QVERIFY(table);
QTextCursor cellCursor = table->cellAt(0, 0).firstCursorPosition();
QImage src(16, 16, QImage::Format_ARGB32_Premultiplied);
src.fill(0xffff0000);
cellCursor.insertImage(src);
QTextBlock block = cellCursor.block();
QRectF r = doc->documentLayout()->blockBoundingRect(block);
QRectF rect(0, 0, r.left() + 1, 64);
QImage img(64, 64, QImage::Format_ARGB32_Premultiplied);
img.fill(0x0);
QImage expected = img;
QPainter p(&img);
doc->drawContents(&p, rect);
p.end();
p.begin(&expected);
r.setWidth(1);
p.fillRect(r, Qt::red);
p.end();
img.save("img.png");
expected.save("expected.png");
QCOMPARE(img, expected);
}
void tst_QTextDocumentLayout::floatingTablePageBreak()
{
doc->clear();
QTextCursor cursor(doc);
QTextTableFormat tableFormat;
tableFormat.setPosition(QTextFrameFormat::FloatLeft);
QTextTable *table = cursor.insertTable(50, 1, tableFormat);
Q_UNUSED(table);
// Make height of document 2/3 of the table, fitting the table into two pages
QSizeF documentSize = doc->size();
documentSize.rheight() *= 2.0 / 3.0;
doc->setPageSize(documentSize);
QCOMPARE(doc->pageCount(), 2);
}
void tst_QTextDocumentLayout::imageAtRightAlignedTab()
{
doc->clear();
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
fmt.setMargin(0);
doc->rootFrame()->setFrameFormat(fmt);
QTextCursor cursor(doc);
QTextBlockFormat blockFormat;
QList<QTextOption::Tab> tabs;
QTextOption::Tab tab;
tab.position = 300;
tab.type = QTextOption::RightTab;
tabs.append(tab);
blockFormat.setTabPositions(tabs);
// First block: text, some of it right-aligned
cursor.insertBlock(blockFormat);
cursor.insertText("first line\t");
cursor.insertText("right-aligned text");
// Second block: text, then right-aligned image
cursor.insertBlock(blockFormat);
cursor.insertText("second line\t");
QImage img(48, 48, QImage::Format_RGB32);
const QString name = QString::fromLatin1("image");
doc->addResource(QTextDocument::ImageResource, QUrl(name), img);
QTextImageFormat imgFormat;
imgFormat.setName(name);
cursor.insertImage(imgFormat);
// Everything should fit into the 300 pixels
qreal bearing = QFontMetricsF(doc->defaultFont()).rightBearing(QLatin1Char('t'));
QCOMPARE(doc->idealWidth(), std::max(300.0, 300.0 - bearing));
}
void tst_QTextDocumentLayout::blockVisibility()
{
QTextCursor cursor(doc);
for (int i = 0; i < 10; ++i) {
if (!doc->isEmpty())
cursor.insertBlock();
cursor.insertText("A");
}
qreal margin = doc->documentMargin();
QSizeF emptySize(2 * margin, 2 * margin);
QSizeF halfSize = doc->size();
halfSize.rheight() -= 2 * margin;
halfSize.rheight() /= 2;
halfSize.rheight() += 2 * margin;
for (int i = 0; i < 10; i += 2) {
QTextBlock block = doc->findBlockByNumber(i);
block.setVisible(false);
doc->markContentsDirty(block.position(), block.length());
}
QCOMPARE(doc->size(), halfSize);
for (int i = 1; i < 10; i += 2) {
QTextBlock block = doc->findBlockByNumber(i);
block.setVisible(false);
doc->markContentsDirty(block.position(), block.length());
}
QCOMPARE(doc->size(), emptySize);
for (int i = 0; i < 10; i += 2) {
QTextBlock block = doc->findBlockByNumber(i);
block.setVisible(true);
doc->markContentsDirty(block.position(), block.length());
}
QCOMPARE(doc->size(), halfSize);
}
void tst_QTextDocumentLayout::largeImage()
{
auto img = QImage(400, 500, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::black);
{
QTextDocument document;
document.addResource(QTextDocument::ImageResource,
QUrl("data://test.png"), QVariant(img));
document.setPageSize({500, 504});
auto html = "<img src=\"data://test.png\">";
document.setHtml(html);
QCOMPARE(document.pageCount(), 2);
}
{
QTextDocument document;
document.addResource(QTextDocument::ImageResource,
QUrl("data://test.png"), QVariant(img));
document.setPageSize({500, 508});
auto html = "<img src=\"data://test.png\">";
document.setHtml(html);
QCOMPARE(document.pageCount(), 1);
}
{
QTextDocument document;
document.addResource(QTextDocument::ImageResource,
QUrl("data://test.png"), QVariant(img));
document.setPageSize({585, 250});
auto html = "<img src=\"data://test.png\">";
document.setHtml(html);
QCOMPARE(document.pageCount(), 3);
}
{
QTextDocument document;
document.addResource(QTextDocument::ImageResource,
QUrl("data://test.png"), QVariant(img));
document.setPageSize({585, 258});
auto html = "<img src=\"data://test.png\">";
document.setHtml(html);
QCOMPARE(document.pageCount(), 2);
}
}
void tst_QTextDocumentLayout::testHitTest()
{
QTextDocument document;
QTextCursor cur(&document);
int topMargin = 20;
//insert 500 blocks into textedit
for (int i = 0; i < 500; i++) {
cur.insertBlock();
cur.insertHtml(QString("block %1").arg(i));
}
//randomly set half the blocks invisible
QTextBlock blk=document.begin();
for (int i = 0; i < 500; i++) {
if (i % 7)
blk.setVisible(0);
blk = blk.next();
}
//set margin for all blocks (not strictly necessary, but makes easier to click in between blocks)
QTextBlockFormat blkfmt;
blkfmt.setTopMargin(topMargin);
cur.movePosition(QTextCursor::Start);
cur.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cur.mergeBlockFormat(blkfmt);
for (int y = cur.selectionStart(); y < cur.selectionEnd(); y += 10) {
QPoint mousePoint(1, y);
int cursorPos = document.documentLayout()->hitTest(mousePoint, Qt::FuzzyHit);
int positionY = document.findBlock(cursorPos).layout()->position().toPoint().y();
//mousePoint is in the rect of the current Block
QVERIFY(positionY - topMargin <= y);
}
}
QTEST_MAIN(tst_QTextDocumentLayout)
#include "tst_qtextdocumentlayout.moc"

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextformat Test:
#####################################################################
qt_internal_add_test(tst_qtextformat
SOURCES
tst_qtextformat.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,796 @@
// 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 <QBuffer>
#include <qcoreapplication.h>
#include <qdebug.h>
#include <qsettings.h>
#include <qtextformat.h>
#include <private/qtextformat_p.h>
#include <qtextdocument.h>
#include <qtextcursor.h>
#include <qtextobject.h>
#include <qtextlayout.h>
#include <qabstracttextdocumentlayout.h>
#ifndef QT_NO_DATASTREAM
# include <qdatastream.h>
#endif
class tst_QTextFormat : public QObject
{
Q_OBJECT
private slots:
void getSetCheck();
void defaultAlignment();
void testQTextCharFormat() const;
void testUnderlinePropertyPrecedence();
void toFormat();
void resolveFont();
void testLetterSpacing();
void testFontStretch();
void getSetTabs();
void testTabsUsed();
void testFontStyleSetters();
void setFont_data();
void setFont();
void setFont_collection_data();
void setFont_collection();
void clearCollection();
#ifndef QT_NO_DATASTREAM
void dataStreamCompatibility();
#endif
};
/*! \internal
This (used to) trigger a crash in:
QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt)
which is most easily produced through QSettings.
*/
void tst_QTextFormat::testQTextCharFormat() const
{
QSettings settings("test", "test");
QTextCharFormat test;
settings.value("test", test);
}
// Testing get/set functions
void tst_QTextFormat::getSetCheck()
{
QTextFormat obj1;
// int QTextFormat::objectIndex()
// void QTextFormat::setObjectIndex(int)
obj1.setObjectIndex(0);
QCOMPARE(0, obj1.objectIndex());
obj1.setObjectIndex(INT_MIN);
QCOMPARE(INT_MIN, obj1.objectIndex());
obj1.setObjectIndex(INT_MAX);
QCOMPARE(INT_MAX, obj1.objectIndex());
}
void tst_QTextFormat::defaultAlignment()
{
QTextBlockFormat fmt;
QVERIFY(!fmt.hasProperty(QTextFormat::BlockAlignment));
QCOMPARE(fmt.intProperty(QTextFormat::BlockAlignment), 0);
QCOMPARE(fmt.alignment(), Qt::AlignLeft);
}
void tst_QTextFormat::testUnderlinePropertyPrecedence()
{
QTextCharFormat format;
// use normal accessors and check internal state
format.setUnderlineStyle(QTextCharFormat::NoUnderline);
QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false);
QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), false);
QCOMPARE(format.property(QTextFormat::FontUnderline).toBool(), false);
QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).toInt(), 0);
format = QTextCharFormat();
format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false);
QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), false);
QCOMPARE(format.property(QTextFormat::FontUnderline).toBool(), true);
QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).toInt(), 1);
format = QTextCharFormat();
format.setUnderlineStyle(QTextCharFormat::DotLine);
QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false);
QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), false);
QCOMPARE(format.property(QTextFormat::FontUnderline).toBool(), false);
QVERIFY(format.property(QTextFormat::TextUnderlineStyle).toInt() > 0);
// override accessors and use setProperty to create a false state.
// then check font()
format = QTextCharFormat();
format.setProperty(QTextCharFormat::FontUnderline, true);
QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false);
QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), true);
QCOMPARE(format.fontUnderline(), true);
QCOMPARE(format.font().underline(), true);
format = QTextCharFormat();
// create conflict. Should use the new property
format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline);
format.setProperty(QTextCharFormat::FontUnderline, false);
QCOMPARE(format.fontUnderline(), true);
QCOMPARE(format.font().underline(), true);
format = QTextCharFormat();
// create conflict. Should use the new property
format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::NoUnderline);
format.setProperty(QTextCharFormat::FontUnderline, true);
QCOMPARE(format.fontUnderline(), false);
QCOMPARE(format.font().underline(), false);
// do it again, but reverse the ordering (we use a QList internally, so test a LOT ;)
// create conflict. Should use the new property
format.setProperty(QTextCharFormat::FontUnderline, false);
format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline);
QCOMPARE(format.fontUnderline(), true);
QCOMPARE(format.font().underline(), true);
format = QTextCharFormat();
// create conflict. Should use the new property
format.setProperty(QTextCharFormat::FontUnderline, true);
format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::NoUnderline);
QCOMPARE(format.fontUnderline(), false);
QCOMPARE(format.font().underline(), false);
}
void tst_QTextFormat::toFormat()
{
{
QTextFormat fmt = QTextFrameFormat();
QCOMPARE(fmt.toFrameFormat().type(), int(QTextFormat::FrameFormat));
}
{
QTextFormat fmt = QTextTableFormat();
QCOMPARE(fmt.toTableFormat().type(), int(QTextFormat::FrameFormat));
QCOMPARE(fmt.toTableFormat().objectType(), int(QTextFormat::TableObject));
}
{
QTextFormat fmt = QTextBlockFormat();
QCOMPARE(fmt.toBlockFormat().type(), int(QTextFormat::BlockFormat));
}
{
QTextFormat fmt = QTextCharFormat();
QCOMPARE(fmt.toCharFormat().type(), int(QTextFormat::CharFormat));
}
{
QTextFormat fmt = QTextListFormat();
QCOMPARE(fmt.toListFormat().type(), int(QTextFormat::ListFormat));
}
}
void tst_QTextFormat::resolveFont()
{
QTextDocument doc;
QTextCharFormat fmt;
fmt.setProperty(QTextFormat::ForegroundBrush, QColor(Qt::blue));
QCOMPARE(fmt.property(QTextFormat::ForegroundBrush).userType(), qMetaTypeId<QColor>());
QCOMPARE(fmt.property(QTextFormat::ForegroundBrush).value<QColor>(), QColor(Qt::blue));
fmt.setProperty(QTextFormat::FontItalic, true);
QTextCursor(&doc).insertText("Test", fmt);
QList<QTextFormat> formats = doc.allFormats();
QCOMPARE(formats.size(), 3);
QCOMPARE(formats.at(2).type(), int(QTextFormat::CharFormat));
fmt = formats.at(2).toCharFormat();
QVERIFY(!fmt.font().underline());
QVERIFY(fmt.hasProperty(QTextFormat::ForegroundBrush));
QFont f;
f.setUnderline(true);
doc.setDefaultFont(f);
formats = doc.allFormats();
fmt = formats.at(2).toCharFormat();
QVERIFY(fmt.font().underline());
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
// verify that deleting a non-existent property does not affect the font resolving
QVERIFY(!fmt.hasProperty(QTextFormat::BackgroundBrush));
fmt.clearProperty(QTextFormat::BackgroundBrush);
QVERIFY(!fmt.hasProperty(QTextFormat::BackgroundBrush));
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
QVERIFY(fmt.font().underline());
// verify that deleting an existent but font _unrelated_ property does not affect the font resolving
QVERIFY(fmt.hasProperty(QTextFormat::ForegroundBrush));
fmt.clearProperty(QTextFormat::ForegroundBrush);
QVERIFY(!fmt.hasProperty(QTextFormat::ForegroundBrush));
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
QVERIFY(fmt.font().underline());
// verify that removing a font property _does_ clear the resolving
QVERIFY(fmt.hasProperty(QTextFormat::FontItalic));
fmt.clearProperty(QTextFormat::FontItalic);
QVERIFY(!fmt.hasProperty(QTextFormat::FontItalic));
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
QVERIFY(!fmt.font().underline());
QVERIFY(!fmt.font().italic());
// reset
fmt = formats.at(2).toCharFormat();
QVERIFY(fmt.font().underline());
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
// verify that _setting_ an unrelated property does _not_ affect the resolving
QVERIFY(!fmt.hasProperty(QTextFormat::IsAnchor));
fmt.setProperty(QTextFormat::IsAnchor, true);
QVERIFY(fmt.hasProperty(QTextFormat::IsAnchor));
QVERIFY(fmt.font().underline());
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
// verify that setting a _related_ font property does affect the resolving
//
QVERIFY(!fmt.hasProperty(QTextFormat::FontStrikeOut));
fmt.setProperty(QTextFormat::FontStrikeOut, true);
QVERIFY(fmt.hasProperty(QTextFormat::FontStrikeOut));
QVERIFY(!fmt.font().underline());
QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline));
QVERIFY(fmt.font().strikeOut());
}
void tst_QTextFormat::testLetterSpacing()
{
QTextCharFormat format;
QCOMPARE(format.hasProperty(QTextFormat::FontLetterSpacing), false);
QCOMPARE(format.hasProperty(QTextFormat::FontLetterSpacingType), false);
format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
QCOMPARE(format.font().letterSpacingType(), QFont::AbsoluteSpacing);
format.setFontLetterSpacing(10.0);
QCOMPARE(format.font().letterSpacing(), 10.0);
QCOMPARE(format.hasProperty(QTextFormat::FontLetterSpacing), true);
QCOMPARE(format.property(QTextFormat::FontLetterSpacing).toDouble(), 10.0);
QCOMPARE(format.property(QTextFormat::FontLetterSpacingType).toInt(), int(QFont::AbsoluteSpacing));
format.setFontLetterSpacingType(QFont::PercentageSpacing);
QCOMPARE(format.font().letterSpacingType(), QFont::PercentageSpacing);
format.setFontLetterSpacing(110.0);
QCOMPARE(format.font().letterSpacing(), 110.0);
QCOMPARE(format.property(QTextFormat::FontLetterSpacing).toDouble(), 110.0);
QCOMPARE(format.property(QTextFormat::FontLetterSpacingType).toInt(), int(QFont::PercentageSpacing));
format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
QCOMPARE(format.font().letterSpacingType(), QFont::AbsoluteSpacing);
format.setFontLetterSpacing(10.0);
QCOMPARE(format.font().letterSpacing(), 10.0);
QCOMPARE(format.property(QTextFormat::FontLetterSpacingType).toInt(), int(QFont::AbsoluteSpacing));
QCOMPARE(format.property(QTextFormat::FontLetterSpacing).toDouble(), 10.0);
}
void tst_QTextFormat::testFontStretch()
{
QTextCharFormat format;
QCOMPARE(format.hasProperty(QTextFormat::FontStretch), false);
format.setFontStretch(130.0);
QCOMPARE(format.property(QTextFormat::FontStretch).toInt(), 130);
}
void tst_QTextFormat::getSetTabs()
{
class Comparator {
public:
Comparator(const QList<QTextOption::Tab> &tabs, const QList<QTextOption::Tab> &tabs2)
{
QCOMPARE(tabs.size(), tabs2.size());
for(int i=0; i < tabs.size(); i++) {
QTextOption::Tab t1 = tabs[i];
QTextOption::Tab t2 = tabs2[i];
QCOMPARE(t1.position, t2.position);
QCOMPARE(t1.type, t2.type);
QCOMPARE(t1.delimiter, t2.delimiter);
}
}
};
QList<QTextOption::Tab> tabs;
QTextBlockFormat format;
format.setTabPositions(tabs);
Comparator c1(tabs, format.tabPositions());
QTextOption::Tab tab1;
tab1.position = 1234;
tabs.append(tab1);
format.setTabPositions(tabs);
Comparator c2(tabs, format.tabPositions());
QTextOption::Tab tab2(3456, QTextOption::RightTab, QChar('x'));
tabs.append(tab2);
format.setTabPositions(tabs);
Comparator c3(tabs, format.tabPositions());
}
void tst_QTextFormat::testTabsUsed()
{
QTextDocument doc;
QTextCursor cursor(&doc);
QList<QTextOption::Tab> tabs;
QTextBlockFormat format;
QTextOption::Tab tab;
tab.position = 100;
tabs.append(tab);
format.setTabPositions(tabs);
cursor.mergeBlockFormat(format);
cursor.insertText("foo\tbar");
//doc.setPageSize(QSizeF(200, 200));
doc.documentLayout()->pageCount(); // force layout;
QTextBlock block = doc.begin();
QTextLayout *layout = block.layout();
QVERIFY(layout);
QCOMPARE(layout->lineCount(), 1);
QTextLine line = layout->lineAt(0);
QCOMPARE(line.cursorToX(4), 100.);
QTextOption option = layout->textOption();
QCOMPARE(option.tabs().size(), tabs.size());
}
void tst_QTextFormat::testFontStyleSetters()
{
QTextCharFormat format;
// test the setters
format.setFontStyleHint(QFont::Serif);
QCOMPARE(format.font().styleHint(), QFont::Serif);
QCOMPARE(format.font().styleStrategy(), QFont::PreferDefault);
format.setFontStyleStrategy(QFont::PreferOutline);
QCOMPARE(format.font().styleStrategy(), QFont::PreferOutline);
// test setting properties through setFont()
QFont font;
font.setStyleHint(QFont::SansSerif, QFont::PreferAntialias);
format.setFont(font);
QCOMPARE(format.font().styleHint(), QFont::SansSerif);
QCOMPARE(format.font().styleStrategy(), QFont::PreferAntialias);
// test kerning
format.setFontKerning(false);
QCOMPARE(format.font().kerning(), false);
format.setFontKerning(true);
QCOMPARE(format.font().kerning(), true);
font.setKerning(false);
format.setFont(font);
QCOMPARE(format.font().kerning(), false);
}
Q_DECLARE_METATYPE(QTextCharFormat)
void tst_QTextFormat::setFont_data()
{
QTest::addColumn<QTextCharFormat>("format1");
QTest::addColumn<QTextCharFormat>("format2");
QTest::addColumn<bool>("overrideAll");
QTextCharFormat format1;
format1.setFontStyleHint(QFont::Serif);
format1.setFontStyleStrategy(QFont::PreferOutline);
format1.setFontCapitalization(QFont::AllUppercase);
format1.setFontKerning(true);
{
QTest::newRow("noop|override") << format1 << format1 << true;
QTest::newRow("noop|inherit") << format1 << format1 << false;
}
{
QTextCharFormat format2;
format2.setFontStyleHint(QFont::SansSerif);
format2.setFontStyleStrategy(QFont::PreferAntialias);
format2.setFontCapitalization(QFont::MixedCase);
format2.setFontKerning(false);
QTest::newRow("coverage|override") << format1 << format2 << true;
QTest::newRow("coverage|inherit") << format1 << format2 << false;
}
{
QTextCharFormat format2;
format2.setFontStyleHint(QFont::SansSerif);
format2.setFontStyleStrategy(QFont::PreferAntialias);
QTest::newRow("partial|override") << format1 << format2 << true;
QTest::newRow("partial|inherit") << format1 << format2 << false;
}
}
void tst_QTextFormat::setFont()
{
QFETCH(QTextCharFormat, format1);
QFETCH(QTextCharFormat, format2);
QFETCH(bool, overrideAll);
QTextCharFormat f;
f.merge(format1);
QCOMPARE((int)f.fontStyleHint(), (int)format1.fontStyleHint());
QCOMPARE((int)f.fontStyleStrategy(), (int)format1.fontStyleStrategy());
QCOMPARE((int)f.fontCapitalization(), (int)format1.fontCapitalization());
QCOMPARE(f.fontKerning(), format1.fontKerning());
QCOMPARE((int)f.font().styleHint(), (int)f.fontStyleHint());
QCOMPARE((int)f.font().styleStrategy(), (int)f.fontStyleStrategy());
QCOMPARE((int)f.font().capitalization(), (int)f.fontCapitalization());
QCOMPARE(f.font().kerning(), f.fontKerning());
f.merge(format2);
QCOMPARE((int)f.font().styleHint(), (int)f.fontStyleHint());
QCOMPARE((int)f.font().styleStrategy(), (int)f.fontStyleStrategy());
QCOMPARE((int)f.font().capitalization(), (int)f.fontCapitalization());
QCOMPARE(f.font().kerning(), f.fontKerning());
if (format2.hasProperty(QTextFormat::FontStyleHint))
QCOMPARE((int)f.font().styleHint(), (int)format2.fontStyleHint());
else
QCOMPARE((int)f.font().styleHint(), (int)format1.fontStyleHint());
if (format2.hasProperty(QTextFormat::FontStyleStrategy))
QCOMPARE((int)f.font().styleStrategy(), (int)format2.fontStyleStrategy());
else
QCOMPARE((int)f.font().styleStrategy(), (int)format1.fontStyleStrategy());
if (format2.hasProperty(QTextFormat::FontCapitalization))
QCOMPARE((int)f.font().capitalization(), (int)format2.fontCapitalization());
else
QCOMPARE((int)f.font().capitalization(), (int)format1.fontCapitalization());
if (format2.hasProperty(QTextFormat::FontKerning))
QCOMPARE(f.font().kerning(), format2.fontKerning());
else
QCOMPARE(f.font().kerning(), format1.fontKerning());
QFont font1 = format1.font();
QFont font2 = format2.font();
f = QTextCharFormat();
{
QTextCharFormat tmp;
tmp.setFont(font1, overrideAll ? QTextCharFormat::FontPropertiesAll
: QTextCharFormat::FontPropertiesSpecifiedOnly);
f.merge(tmp);
}
QCOMPARE((int)f.fontStyleHint(), (int)format1.fontStyleHint());
QCOMPARE((int)f.fontStyleStrategy(), (int)format1.fontStyleStrategy());
QCOMPARE((int)f.fontCapitalization(), (int)format1.fontCapitalization());
QCOMPARE(f.fontKerning(), format1.fontKerning());
QCOMPARE((int)f.font().styleHint(), (int)f.fontStyleHint());
QCOMPARE((int)f.font().styleStrategy(), (int)f.fontStyleStrategy());
QCOMPARE((int)f.font().capitalization(), (int)f.fontCapitalization());
QCOMPARE(f.font().kerning(), f.fontKerning());
{
QTextCharFormat tmp;
tmp.setFont(font2, overrideAll ? QTextCharFormat::FontPropertiesAll
: QTextCharFormat::FontPropertiesSpecifiedOnly);
f.merge(tmp);
}
QCOMPARE((int)f.font().styleHint(), (int)f.fontStyleHint());
QCOMPARE((int)f.font().styleStrategy(), (int)f.fontStyleStrategy());
QCOMPARE((int)f.font().capitalization(), (int)f.fontCapitalization());
QCOMPARE(f.font().kerning(), f.fontKerning());
if (overrideAll || (font2.resolveMask() & QFont::StyleHintResolved))
QCOMPARE((int)f.font().styleHint(), (int)font2.styleHint());
else
QCOMPARE((int)f.font().styleHint(), (int)font1.styleHint());
if (overrideAll || (font2.resolveMask() & QFont::StyleStrategyResolved))
QCOMPARE((int)f.font().styleStrategy(), (int)font2.styleStrategy());
else
QCOMPARE((int)f.font().styleStrategy(), (int)font1.styleStrategy());
if (overrideAll || (font2.resolveMask() & QFont::CapitalizationResolved))
QCOMPARE((int)f.font().capitalization(), (int)font2.capitalization());
else
QCOMPARE((int)f.font().capitalization(), (int)font1.capitalization());
if (overrideAll || (font2.resolveMask() & QFont::KerningResolved))
QCOMPARE(f.font().kerning(), font2.kerning());
else
QCOMPARE(f.font().kerning(), font1.kerning());
}
void tst_QTextFormat::setFont_collection_data()
{
setFont_data();
}
void tst_QTextFormat::setFont_collection()
{
QFETCH(QTextCharFormat, format1);
QFETCH(QTextCharFormat, format2);
QFETCH(bool, overrideAll);
QFont font1 = format1.font();
QFont font2 = format2.font();
{
QTextFormatCollection collection;
collection.setDefaultFont(font1);
int formatIndex = collection.indexForFormat(format1);
QTextCharFormat f = collection.charFormat(formatIndex);
QCOMPARE((int)f.fontStyleHint(), (int)format1.fontStyleHint());
QCOMPARE((int)f.fontStyleStrategy(), (int)format1.fontStyleStrategy());
QCOMPARE((int)f.fontCapitalization(), (int)format1.fontCapitalization());
QCOMPARE(f.fontKerning(), format1.fontKerning());
QCOMPARE((int)f.font().styleHint(), (int)f.fontStyleHint());
QCOMPARE((int)f.font().styleStrategy(), (int)f.fontStyleStrategy());
QCOMPARE((int)f.font().capitalization(), (int)f.fontCapitalization());
QCOMPARE(f.font().kerning(), f.fontKerning());
}
{
QTextFormatCollection collection;
collection.setDefaultFont(font1);
int formatIndex = collection.indexForFormat(format2);
QTextCharFormat f = collection.charFormat(formatIndex);
if (format2.hasProperty(QTextFormat::FontStyleHint))
QCOMPARE((int)f.font().styleHint(), (int)format2.fontStyleHint());
else
QCOMPARE((int)f.font().styleHint(), (int)format1.fontStyleHint());
if (format2.hasProperty(QTextFormat::FontStyleStrategy))
QCOMPARE((int)f.font().styleStrategy(), (int)format2.fontStyleStrategy());
else
QCOMPARE((int)f.font().styleStrategy(), (int)format1.fontStyleStrategy());
if (format2.hasProperty(QTextFormat::FontCapitalization))
QCOMPARE((int)f.font().capitalization(), (int)format2.fontCapitalization());
else
QCOMPARE((int)f.font().capitalization(), (int)format1.fontCapitalization());
if (format2.hasProperty(QTextFormat::FontKerning))
QCOMPARE(f.font().kerning(), format2.fontKerning());
else
QCOMPARE(f.font().kerning(), format1.fontKerning());
}
{
QTextFormatCollection collection;
collection.setDefaultFont(font1);
QTextCharFormat tmp;
tmp.setFont(font1, overrideAll ? QTextCharFormat::FontPropertiesAll
: QTextCharFormat::FontPropertiesSpecifiedOnly);
int formatIndex = collection.indexForFormat(tmp);
QTextCharFormat f = collection.charFormat(formatIndex);
QCOMPARE((int)f.fontStyleHint(), (int)format1.fontStyleHint());
QCOMPARE((int)f.fontStyleStrategy(), (int)format1.fontStyleStrategy());
QCOMPARE((int)f.fontCapitalization(), (int)format1.fontCapitalization());
QCOMPARE(f.fontKerning(), format1.fontKerning());
QCOMPARE((int)f.font().styleHint(), (int)f.fontStyleHint());
QCOMPARE((int)f.font().styleStrategy(), (int)f.fontStyleStrategy());
QCOMPARE((int)f.font().capitalization(), (int)f.fontCapitalization());
QCOMPARE(f.font().kerning(), f.fontKerning());
}
{
QTextFormatCollection collection;
collection.setDefaultFont(font1);
QTextCharFormat tmp;
tmp.setFont(font2, overrideAll ? QTextCharFormat::FontPropertiesAll
: QTextCharFormat::FontPropertiesSpecifiedOnly);
int formatIndex = collection.indexForFormat(tmp);
QTextCharFormat f = collection.charFormat(formatIndex);
if (overrideAll || (font2.resolveMask() & QFont::StyleHintResolved))
QCOMPARE((int)f.font().styleHint(), (int)font2.styleHint());
else
QCOMPARE((int)f.font().styleHint(), (int)font1.styleHint());
if (overrideAll || (font2.resolveMask() & QFont::StyleStrategyResolved))
QCOMPARE((int)f.font().styleStrategy(), (int)font2.styleStrategy());
else
QCOMPARE((int)f.font().styleStrategy(), (int)font1.styleStrategy());
if (overrideAll || (font2.resolveMask() & QFont::CapitalizationResolved))
QCOMPARE((int)f.font().capitalization(), (int)font2.capitalization());
else
QCOMPARE((int)f.font().capitalization(), (int)font1.capitalization());
if (overrideAll || (font2.resolveMask() & QFont::KerningResolved))
QCOMPARE(f.font().kerning(), font2.kerning());
else
QCOMPARE(f.font().kerning(), font1.kerning());
}
}
void tst_QTextFormat::clearCollection()
{
QTextFormatCollection collection;
QFont f;
f.setUnderline(true);
collection.setDefaultFont(f);
QTextCharFormat charFormat;
charFormat.setFontStyleHint(QFont::SansSerif);
int formatIndex = collection.indexForFormat(charFormat);
QCOMPARE(formatIndex, 0);
QTextCharFormat charFormat2;
charFormat2.setUnderlineStyle(QTextCharFormat::SingleUnderline);
int formatIndex2 = collection.indexForFormat(charFormat2);
QCOMPARE(formatIndex2, 1);
QCOMPARE(collection.formats.size(), 2);
QCOMPARE(collection.hashes.size(), 2);
QCOMPARE(collection.defaultFont(), f);
collection.clear();
QCOMPARE(collection.formats.size(), 0);
QCOMPARE(collection.hashes.size(), 0);
QCOMPARE(collection.indexForFormat(charFormat2), 0);
QCOMPARE(collection.formats.size(), 1);
QCOMPARE(collection.hashes.size(), 1);
QCOMPARE(collection.defaultFont(), f); // kept, QTextDocument::clear or setPlainText should not reset the font set by setDefaultFont
}
#ifndef QT_NO_DATASTREAM
void tst_QTextFormat::dataStreamCompatibility()
{
// Make sure that we are still compatible with the old values of QTextFormat::FontLetterSpacingType
// and QTextFormat::FontStretch, when used with earlier QDataStream versions
QTextCharFormat format;
format.setFontStretch(42);
format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
format.setFontFamilies({QLatin1String("Arial")});
// Sanity check
{
QMap<int, QVariant> properties = format.properties();
QVERIFY(properties.contains(QTextFormat::FontLetterSpacingType));
QVERIFY(properties.contains(QTextFormat::FontStretch));
QVERIFY(properties.contains(QTextFormat::FontFamilies));
QVERIFY(!properties.contains(QTextFormat::OldFontLetterSpacingType));
QVERIFY(!properties.contains(QTextFormat::OldFontStretch));
QVERIFY(!properties.contains(QTextFormat::OldFontFamily));
}
QByteArray memory;
// Current stream version
{
{
QBuffer buffer(&memory);
buffer.open(QIODevice::WriteOnly);
QDataStream stream(&buffer);
stream << format;
}
{
QBuffer buffer(&memory);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
QTextFormat other;
stream >> other;
{
QMap<int, QVariant> properties = other.properties();
QVERIFY(properties.contains(QTextFormat::FontLetterSpacingType));
QVERIFY(properties.contains(QTextFormat::FontStretch));
QVERIFY(properties.contains(QTextFormat::FontFamilies));
QVERIFY(!properties.contains(QTextFormat::OldFontLetterSpacingType));
QVERIFY(!properties.contains(QTextFormat::OldFontStretch));
QVERIFY(!properties.contains(QTextFormat::OldFontFamily));
}
}
{
QBuffer buffer(&memory);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
quint32 type;
stream >> type;
QMap<qint32, QVariant> properties;
stream >> properties;
QVERIFY(properties.contains(QTextFormat::FontLetterSpacingType));
QVERIFY(properties.contains(QTextFormat::FontStretch));
QVERIFY(properties.contains(QTextFormat::FontFamilies));
QVERIFY(!properties.contains(QTextFormat::OldFontLetterSpacingType));
QVERIFY(!properties.contains(QTextFormat::OldFontStretch));
QVERIFY(!properties.contains(QTextFormat::OldFontFamily));
}
}
// Qt 5.15 stream version
memory.clear();
{
{
QBuffer buffer(&memory);
buffer.open(QIODevice::WriteOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_15);
stream << format;
}
{
QBuffer buffer(&memory);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_15);
QTextFormat other;
stream >> other;
{
QMap<int, QVariant> properties = other.properties();
QVERIFY(properties.contains(QTextFormat::FontLetterSpacingType));
QVERIFY(properties.contains(QTextFormat::FontStretch));
QVERIFY(properties.contains(QTextFormat::FontFamilies));
QVERIFY(!properties.contains(QTextFormat::OldFontLetterSpacingType));
QVERIFY(!properties.contains(QTextFormat::OldFontStretch));
QVERIFY(!properties.contains(QTextFormat::OldFontFamily));
}
}
{
QBuffer buffer(&memory);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_15);
quint32 type;
stream >> type;
// Verify that old data stream still has the compatibility values
QMap<qint32, QVariant> properties;
stream >> properties;
QVERIFY(!properties.contains(QTextFormat::FontLetterSpacingType));
QVERIFY(!properties.contains(QTextFormat::FontStretch));
QVERIFY(!properties.contains(QTextFormat::FontFamilies));
QVERIFY(properties.contains(QTextFormat::OldFontLetterSpacingType));
QVERIFY(properties.contains(QTextFormat::OldFontStretch));
QVERIFY(properties.contains(QTextFormat::OldFontFamily));
}
}
}
#endif // QT_NO_DATASTREAM
QTEST_MAIN(tst_QTextFormat)
#include "tst_qtextformat.moc"

View File

@ -0,0 +1,23 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
list(APPEND test_data "data/image.png")
list(APPEND test_data "data/image@2x.png")
qt_internal_add_test(tst_qtextimagehandler
SOURCES
tst_qtextimagehandler.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
TESTDATA
${test_data}
)
qt_internal_add_resource(tst_qtextimagehandler "qtextimagehandler"
PREFIX
"/"
FILES
${test_data}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

View File

@ -0,0 +1,79 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QPainter>
#include <private/qtextimagehandler_p.h>
class tst_QTextImageHandler : public QObject
{
Q_OBJECT
public:
tst_QTextImageHandler();
private slots:
void init();
void cleanup();
void cleanupTestCase();
void loadAtNImages_data();
void loadAtNImages();
};
tst_QTextImageHandler::tst_QTextImageHandler()
{
}
void tst_QTextImageHandler::init()
{
}
void tst_QTextImageHandler::cleanup()
{
}
void tst_QTextImageHandler::cleanupTestCase()
{
}
void tst_QTextImageHandler::loadAtNImages_data()
{
QTest::addColumn<QString>("imageFile");
QTest::addRow("file") << QFINDTESTDATA("data/image.png");
QTest::addRow("file_url") << QString("file:/") + QFINDTESTDATA("data/image.png");
QTest::addRow("resource") << ":/data/image.png";
QTest::addRow("qrc_url") << "qrc:/data/image.png";
}
void tst_QTextImageHandler::loadAtNImages()
{
QFETCH(QString, imageFile);
QTextDocument doc;
QTextCursor c(&doc);
c.insertHtml("<img src=\"" + imageFile + "\">");
const auto formats = doc.allFormats();
const auto it = std::find_if(formats.begin(), formats.end(), [](const auto &format){
return format.objectType() == QTextFormat::ImageObject;
});
QVERIFY(it != formats.end());
const QTextImageFormat format = (*it).toImageFormat();
QTextImageHandler handler;
for (const auto &dpr : {1, 2}) {
QImage img(20, 20, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::white);
img.setDevicePixelRatio(dpr);
QPainter p(&img);
handler.drawObject(&p, QRect(0, 0, 20, 20), &doc, 0, format);
p.end();
QVERIFY(!img.isNull());
const auto expectedColor = dpr == 1 ? Qt::red : Qt::green;
QCOMPARE(img.pixelColor(0, 0), expectedColor);
}
}
QTEST_MAIN(tst_QTextImageHandler)
#include "tst_qtextimagehandler.moc"

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextlayout Test:
#####################################################################
qt_internal_add_test(tst_qtextlayout
SOURCES
tst_qtextlayout.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextlist Test:
#####################################################################
qt_internal_add_test(tst_qtextlist
SOURCES
../qtextdocument/common.h
tst_qtextlist.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,404 @@
// 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 <qtextdocument.h>
#include <qtextdocumentfragment.h>
#include <qtextlist.h>
#include <qabstracttextdocumentlayout.h>
#include <qtextcursor.h>
#include "../qtextdocument/common.h"
class tst_QTextList : public QObject
{
Q_OBJECT
private slots:
void init();
void cleanup();
void item();
void autoNumbering();
void autoNumberingRTL();
void autoNumberingPrefixAndSuffix();
void autoNumberingPrefixAndSuffixRTL();
void autoNumberingPrefixAndSuffixHtmlExportImport();
void romanNumbering();
void romanNumberingLimit();
void formatChange();
void cursorNavigation();
void partialRemoval();
void formatReferenceChange();
void ensureItemOrder();
void add();
void defaultIndent();
void blockUpdate();
void numbering_data();
void numbering();
private:
QTextDocument *doc;
QTextCursor cursor;
QTestDocumentLayout *layout;
};
void tst_QTextList::init()
{
doc = new QTextDocument();
layout = new QTestDocumentLayout(doc);
doc->setDocumentLayout(layout);
cursor = QTextCursor(doc);
}
void tst_QTextList::cleanup()
{
cursor = QTextCursor();
delete doc;
doc = 0;
}
void tst_QTextList::item()
{
// this is basically a test for the key() + 1 in QTextList::item.
QTextList *list = cursor.createList(QTextListFormat());
QVERIFY(list->item(0).blockFormat().objectIndex() != -1);
}
void tst_QTextList::autoNumbering()
{
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListLowerAlpha);
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
for (int i = 0; i < 27; ++i)
cursor.insertBlock();
QCOMPARE(list->count(), 28);
QVERIFY(cursor.currentList());
QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), 27);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), QLatin1String("ab."));
}
void tst_QTextList::autoNumberingPrefixAndSuffix()
{
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListLowerAlpha);
fmt.setNumberPrefix("-");
fmt.setNumberSuffix(")");
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
for (int i = 0; i < 27; ++i)
cursor.insertBlock();
QCOMPARE(list->count(), 28);
QVERIFY(cursor.currentList());
QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), 27);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), QLatin1String("-ab)"));
}
void tst_QTextList::autoNumberingPrefixAndSuffixRTL()
{
QTextBlockFormat bfmt;
bfmt.setLayoutDirection(Qt::RightToLeft);
cursor.setBlockFormat(bfmt);
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListUpperAlpha);
fmt.setNumberPrefix("-");
fmt.setNumberSuffix("*");
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
cursor.insertBlock();
QCOMPARE(list->count(), 2);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), QLatin1String("*B-"));
}
void tst_QTextList::autoNumberingPrefixAndSuffixHtmlExportImport()
{
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListLowerAlpha);
fmt.setNumberPrefix("\"");
fmt.setNumberSuffix("#");
fmt.setIndent(10);
// FIXME: Would like to test "'" but there's a problem in the css parser (Scanner::preprocess
// is called before the values are being parsed), so the quoting does not work.
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
for (int i = 0; i < 27; ++i)
cursor.insertBlock();
QCOMPARE(list->count(), 28);
QString htmlExport = doc->toHtml();
QTextDocument importDoc;
importDoc.setHtml(htmlExport);
QTextCursor importCursor(&importDoc);
for (int i = 0; i < 27; ++i)
importCursor.movePosition(QTextCursor::NextBlock);
QVERIFY(importCursor.currentList());
QCOMPARE(importCursor.currentList()->itemNumber(importCursor.block()), 27);
QCOMPARE(importCursor.currentList()->itemText(importCursor.block()), QLatin1String("\"ab#"));
QCOMPARE(importCursor.currentList()->format().indent(), 10);
}
void tst_QTextList::autoNumberingRTL()
{
QTextBlockFormat bfmt;
bfmt.setLayoutDirection(Qt::RightToLeft);
cursor.setBlockFormat(bfmt);
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListUpperAlpha);
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
cursor.insertBlock();
QCOMPARE(list->count(), 2);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), QLatin1String(".B"));
}
void tst_QTextList::romanNumbering()
{
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListUpperRoman);
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
for (int i = 0; i < 4998; ++i)
cursor.insertBlock();
QCOMPARE(list->count(), 4999);
QVERIFY(cursor.currentList());
QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), 4998);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), QLatin1String("MMMMCMXCIX."));
}
void tst_QTextList::romanNumberingLimit()
{
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListLowerRoman);
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
for (int i = 0; i < 4999; ++i)
cursor.insertBlock();
QCOMPARE(list->count(), 5000);
QVERIFY(cursor.currentList());
QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), 4999);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), QLatin1String("?."));
}
void tst_QTextList::formatChange()
{
// testing the formatChanged slot in QTextListManager
/* <initial block>
* 1.
* 2.
*/
QTextList *list = cursor.insertList(QTextListFormat::ListDecimal);
QTextList *firstList = list;
cursor.insertBlock();
QVERIFY(list && list->count() == 2);
QTextBlockFormat bfmt = cursor.blockFormat();
// QCOMPARE(bfmt.object(), list);
bfmt.setObjectIndex(-1);
cursor.setBlockFormat(bfmt);
QCOMPARE(firstList->count(), 1);
}
void tst_QTextList::cursorNavigation()
{
// testing some cursor list methods
/* <initial block>
* 1.
* 2.
*/
cursor.insertList(QTextListFormat::ListDecimal);
cursor.insertBlock();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::NextBlock);
cursor.movePosition(QTextCursor::NextBlock);
QVERIFY(cursor.currentList());
cursor.movePosition(QTextCursor::PreviousBlock);
QVERIFY(cursor.currentList());
QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), 0);
}
void tst_QTextList::partialRemoval()
{
/* this is essentially a test for PieceTable::removeBlock to not miss any
blocks with the blockChanged signal emission that actually get removed.
It creates two lists, like this:
1. Hello World
a. Foobar
and then removes from within the 'Hello World' into the 'Foobar' .
There used to be no emission for the removal of the second (a.) block,
causing list inconsistencies.
*/
QTextList *firstList = cursor.insertList(QTextListFormat::ListDecimal);
QTextCursor selStart = cursor;
selStart.movePosition(QTextCursor::PreviousCharacter);
cursor.insertText("Hello World");
// position it well into the 'hello world' text.
selStart.movePosition(QTextCursor::NextCharacter);
selStart.movePosition(QTextCursor::NextCharacter);
selStart.clearSelection();
QPointer<QTextList> secondList = cursor.insertList(QTextListFormat::ListCircle);
cursor.insertText("Foobar");
// position it into the 'foo bar' text.
cursor.movePosition(QTextCursor::PreviousCharacter);
QTextCursor selEnd = cursor;
// this creates a selection that includes parts of both text-fragments and also the list item of the second list.
QTextCursor selection = selStart;
selection.setPosition(selEnd.position(), QTextCursor::KeepAnchor);
selection.deleteChar(); // deletes the second list
QVERIFY(!secondList);
QVERIFY(firstList->count() > 0);
doc->undo();
}
void tst_QTextList::formatReferenceChange()
{
QTextList *list = cursor.insertList(QTextListFormat::ListDecimal);
cursor.insertText("Some Content...");
cursor.insertBlock(QTextBlockFormat());
cursor.setPosition(list->item(0).position());
int listItemStartPos = cursor.position();
cursor.movePosition(QTextCursor::NextBlock);
int listItemLen = cursor.position() - listItemStartPos;
layout->expect(listItemStartPos, listItemLen, listItemLen);
QTextListFormat fmt = list->format();
fmt.setStyle(QTextListFormat::ListCircle);
list->setFormat(fmt);
QVERIFY(layout->called);
QVERIFY(!layout->error);
}
void tst_QTextList::ensureItemOrder()
{
/*
* Insert a new list item before the first one and verify the blocks
* are sorted after that.
*/
QTextList *list = cursor.insertList(QTextListFormat::ListDecimal);
QTextBlockFormat fmt = cursor.blockFormat();
cursor.movePosition(QTextCursor::Start);
cursor.insertBlock(fmt);
QCOMPARE(list->item(0).position(), 1);
QCOMPARE(list->item(1).position(), 2);
}
void tst_QTextList::add()
{
QTextList *list = cursor.insertList(QTextListFormat::ListDecimal);
cursor.insertBlock(QTextBlockFormat());
QCOMPARE(list->count(), 1);
cursor.insertBlock(QTextBlockFormat());
list->add(cursor.block());
QCOMPARE(list->count(), 2);
}
// Task #72036
void tst_QTextList::defaultIndent()
{
QTextListFormat fmt;
QCOMPARE(fmt.indent(), 1);
}
void tst_QTextList::blockUpdate()
{
// three items
QTextList *list = cursor.insertList(QTextListFormat::ListDecimal);
cursor.insertBlock();
cursor.insertBlock();
// remove second, needs also update on the third
// since the numbering might have changed
const int len = cursor.position() + cursor.block().length() - 1;
layout->expect(1, len, len);
list->remove(list->item(1));
QVERIFY(!layout->error);
}
void tst_QTextList::numbering_data()
{
QTest::addColumn<int>("format");
QTest::addColumn<int>("number");
QTest::addColumn<QString>("result");
QTest::newRow("E.") << int(QTextListFormat::ListUpperAlpha) << 5 << "E.";
QTest::newRow("abc.") << int(QTextListFormat::ListLowerAlpha) << (26 + 2) * 26 + 3 << "abc.";
QTest::newRow("12.") << int(QTextListFormat::ListDecimal) << 12 << "12.";
QTest::newRow("XXIV.") << int(QTextListFormat::ListUpperRoman) << 24 << "XXIV.";
QTest::newRow("VIII.") << int(QTextListFormat::ListUpperRoman) << 8 << "VIII.";
QTest::newRow("xxx.") << int(QTextListFormat::ListLowerRoman) << 30 << "xxx.";
QTest::newRow("xxix.") << int(QTextListFormat::ListLowerRoman) << 29 << "xxix.";
// QTest::newRow("xxx. alpha") << int(QTextListFormat::ListLowerAlpha) << (24 * 26 + 24) * 26 + 24 << "xxx."; //Too slow
}
void tst_QTextList::numbering()
{
QFETCH(int, format);
QFETCH(int, number);
QFETCH(QString, result);
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::Style(format));
QTextList *list = cursor.createList(fmt);
QVERIFY(list);
for (int i = 1; i < number; ++i)
cursor.insertBlock();
QCOMPARE(list->count(), number);
QVERIFY(cursor.currentList());
QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), number - 1);
QCOMPARE(cursor.currentList()->itemText(cursor.block()), result);
}
QTEST_MAIN(tst_QTextList)
#include "tst_qtextlist.moc"

View File

@ -0,0 +1,22 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextmarkdownimporter Test:
#####################################################################
# Collect test data
list(APPEND test_data "data/thematicBreaks.md")
list(APPEND test_data "data/headingBulletsContinuations.md")
list(APPEND test_data "data/fuzz20450.md")
list(APPEND test_data "data/fuzz20580.md")
qt_internal_add_test(tst_qtextmarkdownimporter
SOURCES
tst_qtextmarkdownimporter.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
TESTDATA ${test_data}
)

View File

@ -0,0 +1,5 @@
<t><EFBFBD>
* <20>
<09>
* <20>

View File

@ -0,0 +1 @@
|

View File

@ -0,0 +1,28 @@
# heading
- bullet 1
continuation line 1, indented via tab
- bullet 2
continuation line 2, indented via 4 spaces
- bullet 3
continuation paragraph 3, indented via tab
- bullet 3.1
continuation paragraph 3.1, indented via 4 spaces
- bullet 3.2
continuation line, indented via 2 tabs
- bullet 4
continuation paragraph 4, indented via 4 spaces
and continuing onto another line too
- bullet 5
continuation paragraph 5, indented via 2 spaces and continuing onto another
line too
- bullet 6
plain old paragraph at the end

View File

@ -0,0 +1,17 @@
Heading
-------
***
stars
- bullet
** not a bullet or a rule, just two stars
- [ ] unchecked
--- indented too far, so not a rule
* * *
stars with tabs between
***
stars with whitespace after
---
hyphens with whitespace after
_____
underscores with whitespace after

View File

@ -0,0 +1,568 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QBuffer>
#include <QDebug>
#include <QFontInfo>
#include <QTextDocument>
#include <QTextCursor>
#include <QTextBlock>
#include <QTextDocumentFragment>
#include <QTextList>
#include <QTextTable>
#include <QLoggingCategory>
#include <private/qtextmarkdownimporter_p.h>
// #define DEBUG_WRITE_HTML
Q_LOGGING_CATEGORY(lcTests, "qt.text.tests")
static const QChar LineBreak = QChar(0x2028);
static const QChar Tab = QLatin1Char('\t');
static const QChar Space = QLatin1Char(' ');
static const QChar Period = QLatin1Char('.');
class tst_QTextMarkdownImporter : public QObject
{
Q_OBJECT
private slots:
void headingBulletsContinuations();
void thematicBreaks();
void lists_data();
void lists();
void nestedSpans_data();
void nestedSpans();
void avoidBlankLineAtBeginning_data();
void avoidBlankLineAtBeginning();
void fragmentsAndProperties_data();
void fragmentsAndProperties();
void pathological_data();
void pathological();
void fencedCodeBlocks_data();
void fencedCodeBlocks();
private:
bool isMainFontFixed();
public:
enum CharFormat {
Normal = 0x0,
Italic = 0x1,
Bold = 0x02,
Underlined = 0x04,
Strikeout = 0x08,
Mono = 0x10,
Link = 0x20
};
Q_ENUM(CharFormat)
Q_DECLARE_FLAGS(CharFormats, CharFormat)
};
Q_DECLARE_METATYPE(tst_QTextMarkdownImporter::CharFormats)
Q_DECLARE_OPERATORS_FOR_FLAGS(tst_QTextMarkdownImporter::CharFormats)
bool tst_QTextMarkdownImporter::isMainFontFixed()
{
bool ret = QFontInfo(QGuiApplication::font()).fixedPitch();
if (ret) {
qCWarning(lcTests) << "QFontDatabase::GeneralFont is monospaced: markdown writing is likely to use too many backticks";
qCWarning(lcTests) << "system fonts: fixed" << QFontDatabase::systemFont(QFontDatabase::FixedFont)
<< "fixed?" << QFontInfo(QFontDatabase::systemFont(QFontDatabase::FixedFont)).fixedPitch()
<< "general" << QFontDatabase::systemFont(QFontDatabase::GeneralFont);
}
return ret;
}
void tst_QTextMarkdownImporter::headingBulletsContinuations()
{
const QStringList expectedBlocks = QStringList() <<
"heading" <<
"bullet 1 continuation line 1, indented via tab" <<
"bullet 2 continuation line 2, indented via 4 spaces" <<
"bullet 3" <<
"continuation paragraph 3, indented via tab" <<
"bullet 3.1" <<
"continuation paragraph 3.1, indented via 4 spaces" <<
"bullet 3.2 continuation line, indented via 2 tabs" <<
"bullet 4" <<
"continuation paragraph 4, indented via 4 spaces and continuing onto another line too" <<
"bullet 5" <<
// indenting by only 2 spaces is perhaps non-standard but currently is OK
"continuation paragraph 5, indented via 2 spaces and continuing onto another line too" <<
"bullet 6" <<
"plain old paragraph at the end";
QFile f(QFINDTESTDATA("data/headingBulletsContinuations.md"));
QVERIFY(f.open(QFile::ReadOnly | QIODevice::Text));
QString md = QString::fromUtf8(f.readAll());
f.close();
QTextDocument doc;
QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, md);
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
QStringList::const_iterator expectedIt = expectedBlocks.constBegin();
int i = 0;
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check whether we got the right child block
QTextBlock block = iterator.currentBlock();
QCOMPARE(block.text().contains(LineBreak), false);
QCOMPARE(block.text().contains(Tab), false);
QVERIFY(!block.text().startsWith(Space));
int expectedIndentation = 0;
if (block.text().contains(QLatin1String("continuation paragraph")))
expectedIndentation = (block.text().contains(Period) ? 2 : 1);
qCDebug(lcTests) << i << "child block" << block.text() << "indentation" << block.blockFormat().indent();
QVERIFY(expectedIt != expectedBlocks.constEnd());
QCOMPARE(block.text(), *expectedIt);
if (i > 2)
QCOMPARE(block.blockFormat().indent(), expectedIndentation);
++iterator;
++expectedIt;
++i;
}
QCOMPARE(expectedIt, expectedBlocks.constEnd());
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/headingBulletsContinuations.html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
}
void tst_QTextMarkdownImporter::thematicBreaks()
{
int horizontalRuleCount = 0;
int textLinesCount = 0;
QFile f(QFINDTESTDATA("data/thematicBreaks.md"));
QVERIFY(f.open(QFile::ReadOnly | QIODevice::Text));
QString md = QString::fromUtf8(f.readAll());
f.close();
QTextDocument doc;
QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, md);
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
int i = 0;
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check whether the block is text or a horizontal rule
QTextBlock block = iterator.currentBlock();
if (block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth))
++horizontalRuleCount;
else if (!block.text().isEmpty())
++textLinesCount;
qCDebug(lcTests) << i << (block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth) ? QLatin1String("- - -") : block.text());
++iterator;
++i;
}
QCOMPARE(horizontalRuleCount, 5);
QCOMPARE(textLinesCount, 9);
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/thematicBreaks.html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
}
void tst_QTextMarkdownImporter::lists_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<int>("expectedItemCount");
QTest::addColumn<bool>("expectedEmptyItems");
QTest::addColumn<QString>("rewrite");
// Some of these cases show odd behavior, which is subject to change
// as the importer and the writer are tweaked to fix bugs over time.
QTest::newRow("dot newline") << ".\n" << 0 << true << ".\n\n";
QTest::newRow("number dot newline") << "1.\n" << 1 << true << "1. \n";
QTest::newRow("star newline") << "*\n" << 1 << true << "* \n";
QTest::newRow("hyphen newline") << "-\n" << 1 << true << "- \n";
QTest::newRow("hyphen space newline") << "- \n" << 1 << true << "- \n";
QTest::newRow("hyphen space letter newline") << "- a\n" << 1 << false << "- a\n";
QTest::newRow("hyphen nbsp newline") <<
QString::fromUtf8("-\u00A0\n") << 0 << true << "-\u00A0\n\n";
QTest::newRow("nested empty lists") << "*\n *\n *\n" << 1 << true << " * \n";
QTest::newRow("list nested in empty list") << "-\n * a\n" << 2 << false << "- \n * a\n";
QTest::newRow("lists nested in empty lists")
<< "-\n * a\n * b\n- c\n *\n + d\n" << 5 << false
<< "- \n * a\n * b\n- c *\n + d\n";
QTest::newRow("numeric lists nested in empty lists")
<< "- \n 1. a\n 2. b\n- c\n 1.\n + d\n" << 4 << false
<< "- \n 1. a\n 2. b\n- c 1. + d\n";
QTest::newRow("styled spans in list items")
<< "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n" << 5 << false
<< "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n";
}
void tst_QTextMarkdownImporter::lists()
{
QFETCH(QString, input);
QFETCH(int, expectedItemCount);
QFETCH(bool, expectedEmptyItems);
QFETCH(QString, rewrite);
QTextDocument doc;
doc.setMarkdown(input); // QTBUG-78870 : don't crash
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
int i = 0;
int itemCount = 0;
bool emptyItems = true;
QString firstItemFontFamily;
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check whether the block is text or a horizontal rule
QTextBlock block = iterator.currentBlock();
if (block.textList()) {
++itemCount;
if (!block.text().isEmpty())
emptyItems = false;
}
qCDebug(lcTests, "%d %s%s", i,
(block.textList() ? "<li>" : "<p>"), qPrintable(block.text()));
QTextCharFormat listItemFmt = block.charFormat();
QFont listItemFont = listItemFmt.font();
// QTextDocumentLayoutPrivate::drawListItem() uses listItemFont to render numbers in an ordered list.
// We want that to be consistent, regardless whether the list item's text begins with a styled span.
if (firstItemFontFamily.isEmpty())
firstItemFontFamily = listItemFont.family();
else
QCOMPARE(listItemFont.family(), firstItemFontFamily);
QCOMPARE(listItemFont.bold(), false);
QCOMPARE(listItemFont.italic(), false);
QCOMPARE(listItemFont.underline(), false);
QCOMPARE(listItemFont.fixedPitch(), false);
QCOMPARE(listItemFmt.fontItalic(), false);
QCOMPARE(listItemFmt.fontUnderline(), false);
QCOMPARE(listItemFmt.fontFixedPitch(), false);
++iterator;
++i;
}
QCOMPARE(itemCount, expectedItemCount);
QCOMPARE(emptyItems, expectedEmptyItems);
if (doc.toMarkdown() != rewrite && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(doc.toMarkdown(), rewrite);
}
void tst_QTextMarkdownImporter::nestedSpans_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<int>("wordToCheck");
QTest::addColumn<CharFormats>("expectedFormat");
QTest::newRow("bold italic")
<< "before ***bold italic*** after"
<< 1 << (Bold | Italic);
QTest::newRow("bold underlined")
<< "before **_bold underlined_** after"
<< 1 << (Bold | Underlined);
QTest::newRow("italic underlined")
<< "before *_italic underlined_* after"
<< 1 << (Italic | Underlined);
QTest::newRow("bold strikeout")
<< "before **~~bold strikeout~~** after"
<< 1 << (Bold | Strikeout);
QTest::newRow("italic strikeout")
<< "before *~~italic strikeout~~* after"
<< 1 << (Italic | Strikeout);
QTest::newRow("bold italic strikeout")
<< "before ***~~bold italic strikeout~~*** after"
<< 1 << (Bold | Italic | Strikeout);
QTest::newRow("bold link text")
<< "before [**bold link**](https://qt.io) after"
<< 1 << (Bold | Link);
QTest::newRow("italic link text")
<< "before [*italic link*](https://qt.io) after"
<< 1 << (Italic | Link);
QTest::newRow("bold italic link text")
<< "before [***bold italic link***](https://qt.io) after"
<< 1 << (Bold | Italic | Link);
QTest::newRow("strikeout link text")
<< "before [~~strikeout link~~](https://qt.io) after"
<< 1 << (Strikeout | Link);
QTest::newRow("strikeout bold italic link text")
<< "before [~~***strikeout bold italic link***~~](https://qt.io) after"
<< 1 << (Strikeout | Bold | Italic | Link);
QTest::newRow("bold image alt")
<< "before [**bold image alt**](/path/to/image.png) after"
<< 1 << (Bold | Link);
QTest::newRow("bold strikeout italic image alt")
<< "before [**~~*bold strikeout italic image alt*~~**](/path/to/image.png) after"
<< 1 << (Strikeout | Bold | Italic | Link);
// code spans currently override all surrounding formatting
QTest::newRow("code in italic span")
<< "before *italic `code` and* after"
<< 2 << (Mono | Normal);
// but the format after the code span ends should revert to what it was before
QTest::newRow("code in italic strikeout bold span")
<< "before *italic ~~strikeout **bold `code` and**~~* after"
<< 5 << (Bold | Italic | Strikeout);
}
void tst_QTextMarkdownImporter::nestedSpans()
{
QFETCH(QString, input);
QFETCH(int, wordToCheck);
QFETCH(CharFormats, expectedFormat);
QTextDocument doc;
doc.setMarkdown(input);
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check the QTextCharFormat of the specified word
QTextCursor cur(iterator.currentBlock());
cur.movePosition(QTextCursor::NextWord, QTextCursor::MoveAnchor, wordToCheck);
cur.select(QTextCursor::WordUnderCursor);
QTextCharFormat fmt = cur.charFormat();
qCDebug(lcTests) << "word" << wordToCheck << cur.selectedText() << "font" << fmt.font()
<< "weight" << fmt.fontWeight() << "italic" << fmt.fontItalic()
<< "underlined" << fmt.fontUnderline()
<< "strikeout" << fmt.fontStrikeOut() << "anchor" << fmt.isAnchor()
<< "monospace" << QFontInfo(fmt.font()).fixedPitch() // depends on installed fonts (QTBUG-75649)
<< fmt.fontFixedPitch()
<< fmt.hasProperty(QTextFormat::FontFixedPitch)
<< "expected" << expectedFormat;
QCOMPARE(fmt.fontWeight() > QFont::Normal, expectedFormat.testFlag(Bold));
QCOMPARE(fmt.fontItalic(), expectedFormat.testFlag(Italic));
QCOMPARE(fmt.fontUnderline(), expectedFormat.testFlag(Underlined));
QCOMPARE(fmt.fontStrikeOut(), expectedFormat.testFlag(Strikeout));
QCOMPARE(fmt.isAnchor(), expectedFormat.testFlag(Link));
QCOMPARE(fmt.fontFixedPitch(), expectedFormat.testFlag(Mono));
QCOMPARE(fmt.hasProperty(QTextFormat::FontFixedPitch), expectedFormat.testFlag(Mono));
++iterator;
}
}
void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<int>("expectedNumberOfParagraphs");
QTest::newRow("Text block") << QString("Markdown text") << 1;
QTest::newRow("Headline") << QString("Markdown text\n============") << 1;
QTest::newRow("Code block") << QString(" Markdown text") << 1;
QTest::newRow("Unordered list") << QString("* Markdown text") << 1;
QTest::newRow("Ordered list") << QString("1. Markdown text") << 1;
QTest::newRow("Blockquote") << QString("> Markdown text") << 1;
}
void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning() // QTBUG-81060
{
QFETCH(QString, input);
QFETCH(int, expectedNumberOfParagraphs);
QTextDocument doc;
QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, input);
QTextFrame::iterator iterator = doc.rootFrame()->begin();
int i = 0;
while (!iterator.atEnd()) {
QTextBlock block = iterator.currentBlock();
// Make sure there is no empty paragraph at the beginning of the document
if (i == 0)
QVERIFY(!block.text().isEmpty());
++iterator;
++i;
}
QCOMPARE(i, expectedNumberOfParagraphs);
}
void tst_QTextMarkdownImporter::fragmentsAndProperties_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<int>("fragmentToCheck");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<QTextFormat::Property>("propertyToCheck");
QTest::addColumn<QVariant>("expectedPropertyValue");
QTest::addColumn<int>("expectedNumberOfBlocks");
QTest::addColumn<int>("expectedNumberOfFragments");
QTest::newRow("entitiesInHtmlFontBlock") // QTBUG-94245
<< QString("<font color='red'>&lt;123 test&gt;</font>&nbsp;test")
<< 0 << "<123 test>" << QTextFormat::ForegroundBrush << QVariant(QBrush(QColor("red")))
<< 1 << 2;
QTest::newRow("entitiesInHtmlBoldBlock") // QTBUG-91222
<< QString("<b>x&amp;lt;</b>")
<< 0 << "x&lt;" << QTextFormat::FontWeight << QVariant(700)
<< 1 << 1;
}
void tst_QTextMarkdownImporter::fragmentsAndProperties()
{
QFETCH(QString, input);
QFETCH(int, fragmentToCheck);
QFETCH(QString, expectedText);
QFETCH(QTextFormat::Property, propertyToCheck);
QFETCH(QVariant, expectedPropertyValue);
QFETCH(int, expectedNumberOfBlocks);
QFETCH(int, expectedNumberOfFragments);
QTextDocument doc;
QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, input);
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
QTextFrame::iterator blockIter = doc.rootFrame()->begin();
int blockCount = 0;
int fragCount = 0;
while (!blockIter.atEnd()) {
QTextBlock block = blockIter.currentBlock();
auto fragIter = block.begin();
while (!fragIter.atEnd()) {
auto frag = fragIter.fragment();
qCDebug(lcTests) << "fragment" << fragCount << ':' << frag.text() << Qt::hex << frag.charFormat().properties();
if (fragCount == fragmentToCheck) {
QVariant prop = frag.charFormat().property(propertyToCheck);
QCOMPARE(prop, expectedPropertyValue);
QCOMPARE(frag.text(), expectedText);
}
++fragIter;
++fragCount;
}
++blockIter;
++blockCount;
}
QCOMPARE(blockCount, expectedNumberOfBlocks);
QCOMPARE(fragCount, expectedNumberOfFragments);
}
void tst_QTextMarkdownImporter::pathological_data()
{
QTest::addColumn<QString>("warning");
QTest::newRow("fuzz20450") << "attempted to insert into a list that no longer exists";
QTest::newRow("fuzz20580") << "";
}
void tst_QTextMarkdownImporter::pathological() // avoid crashing on crazy input
{
QFETCH(QString, warning);
QString filename = QLatin1String("data/") + QTest::currentDataTag() + QLatin1String(".md");
QFile f(QFINDTESTDATA(filename));
QVERIFY(f.open(QFile::ReadOnly));
#ifdef QT_NO_DEBUG
Q_UNUSED(warning);
#else
if (!warning.isEmpty())
QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
#endif
QTextDocument().setMarkdown(f.readAll());
}
void tst_QTextMarkdownImporter::fencedCodeBlocks_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<int>("expectedCodeBlockCount");
QTest::addColumn<int>("expectedPlainBlockCount");
QTest::addColumn<QString>("expectedLanguage");
QTest::addColumn<QString>("expectedFenceChar");
QTest::addColumn<QString>("rewrite");
QTest::newRow("backtick fence with language")
<< "```pseudocode\nprint('hello world\\n')\n```\n"
<< 1 << 0 << "pseudocode" << "`"
<< "```pseudocode\nprint('hello world\\n')\n```\n\n";
QTest::newRow("tilde fence with language")
<< "~~~pseudocode\nprint('hello world\\n')\n~~~\n"
<< 1 << 0 << "pseudocode" << "~"
<< "~~~pseudocode\nprint('hello world\\n')\n~~~\n\n";
QTest::newRow("embedded backticks")
<< "```\nnone `one` ``two``\n```\nplain\n```\n```three``` ````four````\n```\nplain\n"
<< 2 << 2 << QString() << "`"
<< "```\nnone `one` ``two``\n```\nplain\n\n```\n```three``` ````four````\n```\nplain\n\n";
}
void tst_QTextMarkdownImporter::fencedCodeBlocks()
{
QFETCH(QString, input);
QFETCH(int, expectedCodeBlockCount);
QFETCH(int, expectedPlainBlockCount);
QFETCH(QString, expectedLanguage);
QFETCH(QString, expectedFenceChar);
QFETCH(QString, rewrite);
QTextDocument doc;
doc.setMarkdown(input);
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
int codeBlockCount = 0;
int plainBlockCount = 0;
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check whether the block is code or plain
QTextBlock block = iterator.currentBlock();
const bool codeBlock = block.blockFormat().hasProperty(QTextFormat::BlockCodeFence);
QCOMPARE(block.blockFormat().nonBreakableLines(), codeBlock);
QCOMPARE(block.blockFormat().stringProperty(QTextFormat::BlockCodeLanguage), codeBlock ? expectedLanguage : QString());
if (codeBlock) {
QCOMPARE(block.blockFormat().stringProperty(QTextFormat::BlockCodeFence), expectedFenceChar);
++codeBlockCount;
} else {
++plainBlockCount;
}
qCDebug(lcTests) << (codeBlock ? "code" : "text") << block.text() << block.charFormat().fontFamilies();
++iterator;
}
QCOMPARE(codeBlockCount, expectedCodeBlockCount);
QCOMPARE(plainBlockCount, expectedPlainBlockCount);
if (doc.toMarkdown() != rewrite && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(doc.toMarkdown(), rewrite);
}
QTEST_MAIN(tst_QTextMarkdownImporter)
#include "tst_qtextmarkdownimporter.moc"

View File

@ -0,0 +1,20 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextmarkdownwriter Test:
#####################################################################
# Collect test data
list(APPEND test_data "data/example.md")
list(APPEND test_data "data/blockquotes.md")
qt_internal_add_test(tst_qtextmarkdownwriter
SOURCES
tst_qtextmarkdownwriter.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
TESTDATA ${test_data}
)

View File

@ -0,0 +1,62 @@
In 1958, Mahatma Gandhi was quoted as follows:
> The Earth provides enough to satisfy every man's need but not for every man's
> greed.
In [The CommonMark Specification](https://spec.commonmark.org/0.29/) John
MacFarlane writes:
> What distinguishes Markdown from many other lightweight markup syntaxes,
> which are often easier to write, is its readability. As Gruber writes:
> > The overriding design goal for Markdown's formatting syntax is to make it
> > as readable as possible. The idea is that a Markdown-formatted document should
> > be publishable as-is, as plain text, without looking like it's been marked up
> > with tags or formatting instructions. (
> > <http://daringfireball.net/projects/markdown/> )
> The point can be illustrated by comparing a sample of AsciiDoc with an
> equivalent sample of Markdown. Here is a sample of AsciiDoc from the AsciiDoc
> manual:
> ```AsciiDoc
> 1. List item one.
> +
> List item one continued with a second paragraph followed by an
> Indented block.
> +
> .................
> $ ls *.sh
> $ mv *.sh ~/tmp
> .................
> +
> List item continued with a third paragraph.
>
> 2. List item two continued with an open block.
> ...
> ```
The quotation includes an embedded quotation and a code quotation and ends with
an ellipsis due to being incomplete.
Now let's have an indented code block:
#include <stdio.h>
int main(void)
{
printf("# hello markdown\n");
return 0;
}
and end with a fenced code block:
~~~pseudocode
#include <something.h>
#include <else.h>
a block {
a statement;
another statement;
}
~~~

View File

@ -0,0 +1,96 @@
# QTextEdit
The QTextEdit widget is an advanced editor that supports formatted rich text.
It can be used to display HTML and other rich document formats. Internally,
QTextEdit uses the QTextDocument class to describe both the high-level
structure of each document and the low-level formatting of paragraphs.
If you are viewing this document in the textedit example, you can edit this
document to explore Qt's rich text editing features. We have included some
comments in each of the following sections to encourage you to experiment.
## Font and Paragraph Styles
QTextEdit supports **bold**, *italic*, _underline_ and ~~strikethrough~~ font
styles, and can display multicolored text. Font families such as Times New
Roman and `Courier` can also be used directly. *If you place the cursor in a
region of styled text, the controls in the tool bars will change to reflect the
current style.*
Paragraphs can be formatted so that the text is left-aligned, right-aligned,
centered, or fully justified.
*Try changing the alignment of some text and resize the editor to see how the
text layout changes.*
## Lists
Different kinds of lists can be included in rich text documents. Standard
bullet lists can be nested, using different symbols for each level of the list:
- Disc symbols are typically used for top-level list items.
* Circle symbols can be used to distinguish between items in lower-level
lists.
+ Square symbols provide a reasonable alternative to discs and circles.
Ordered lists can be created that can be used for tables of contents. Different
characters can be used to enumerate items, and we can use both Roman and Arabic
numerals in the same list structure:
1. Introduction
2. Qt Tools
1) Qt Assistant
2) Qt Designer
1. Form Editor
2. Component Architecture
3) Qt Linguist
The list will automatically be renumbered if you add or remove items. *Try
adding new sections to the above list or removing existing item to see the
numbers change.*
## Images
Inline images are treated like ordinary ranges of characters in the text
editor, so they flow with the surrounding text. Images can also be selected in
the same way as text, making it easy to cut, copy, and paste them.
![image](images/logo32.png) *Try to select this image by clicking and dragging
over it with the mouse, or use the text cursor to select it by holding down
Shift and using the arrow keys. You can then cut or copy it, and paste it into
different parts of this document.*
## Tables
QTextEdit can arrange and format tables, supporting features such as row and
column spans, text formatting within cells, and size constraints for columns.
| |Development Tools |Programming Techniques |Graphical User Interfaces|
|-------------|------------------------------------|---------------------------|-------------------------|
|9:00 - 11:00 |Introduction to Qt |||
|11:00 - 13:00|Using qmake |Object-oriented Programming|Layouts in Qt |
|13:00 - 15:00|Qt Designer Tutorial |Extreme Programming |Writing Custom Styles |
|15:00 - 17:00|Qt Linguist and Internationalization|Test-Driven Development | |
*Try adding text to the cells in the table and experiment with the alignment of
the paragraphs.*
## Hyperlinks
QTextEdit is designed to support hyperlinks between documents, and this feature
is used extensively in
[Qt Assistant](http://doc.qt.io/qt-5/qtassistant-index.html). Hyperlinks are
automatically created when an HTML file is imported into an editor. Since the
rich text framework supports hyperlinks natively, they can also be created
programmatically.
## Undo and Redo
Full support for undo and redo operations is built into QTextEdit and the
underlying rich text framework. Operations on a document can be packaged
together to make editing a more comfortable experience for the user.
*Try making changes to this document and press `Ctrl+Z` to undo them. You can
always recover the original contents of the document.*

View File

@ -0,0 +1,12 @@
# heading 1
- list item 1
- list item 2
## heading 2
1) list item 1
2) list item 2
the end paragraph

View File

@ -0,0 +1,28 @@
A series of links.
[link](/uri)
[link]()
[link](/uri "title")
[link](/uri "àbcdè")
[link](/uri "title title \" title title")
[link](/url "title \"&quot;")
[link](/url "title
title
title title
\"title\" title \"
title")
* [link](/url "title")
* [link](/url)
* [link](/url "title
title title")
* nonlink
Qt has the <https://qt.io> site

View File

@ -0,0 +1,31 @@
- something happens in the debugger like this:
```
1 QQuickEventPoint::setGrabberItem qquickevents.cpp 869 0x7ffff7a963f2
2 QQuickItem::grabMouse qquickitem.cpp 7599 0x7ffff7abea29
3 QQuickWindowPrivate::deliverMatchingPointsToItem qquickwindow.cpp 2738 0x7ffff7aea34c
4 QQuickWindowPrivate::deliverPressOrReleaseEvent qquickwindow.cpp 2692 0x7ffff7ae9e57
5 QQuickWindowPrivate::deliverMouseEvent qquickwindow.cpp 1911 0x7ffff7ae561b
6 QQuickWindowPrivate::deliverPointerEvent qquickwindow.cpp 2454 0x7ffff7ae888c
7 QQuickWindowPrivate::handleMouseEvent qquickwindow.cpp 2282 0x7ffff7ae7f1a
8 QQuickWindow::mousePressEvent qquickwindow.cpp 2249 0x7ffff7ae7bf5
9 QQuickView::mousePressEvent qquickview.cpp 626 0x7ffff7bd6bad
10 QWindow::event qwindow.cpp 2258 0x7ffff70b2c54
```
and then I want to explain something about it.
- something I tried to fix it:
```c++
item->ungrab();
```
- still didn't fix it, expecting a breakthrough any day now
- some sort of miracle
- profit!
- Alternatively we can have a non-indented fenced code block under a list item:
```qml
import QtQuick
Text { text: "hello world" }
```
- but that means the code block is not part of the list item.

View File

@ -0,0 +1,13 @@
[The CommonMark Specification](https://spec.commonmark.org/0.29/) is the
conservative formal specification of the Markdown format, while
[GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown)
adds extra features such as task lists and tables.
Qt owes thanks to the authors of the [MD4C parser](https://github.com/mity/md4c)
for making markdown import possible. The QTextMarkdownWriter class does not
have such dependencies, and also has not yet been tested as extensively, so we
do not yet guarantee that we are able to rewrite every Markdown document that
you are able to read and display with Text or QTextEdit. But you are free to
write [bugs](https://bugreports.qt.io) about any troublesome cases that you
encounter.

View File

@ -0,0 +1,533 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QTextDocument>
#include <QTextCursor>
#include <QTextBlock>
#include <QTextList>
#include <QTextTable>
#include <QBuffer>
#include <QDebug>
#include <QFontInfo>
#include <QLoggingCategory>
#include <private/qtextmarkdownwriter_p.h>
Q_LOGGING_CATEGORY(lcTests, "qt.text.tests")
// #define DEBUG_WRITE_OUTPUT
class tst_QTextMarkdownWriter : public QObject
{
Q_OBJECT
public slots:
void init();
void cleanup();
private slots:
void testWriteParagraph_data();
void testWriteParagraph();
void testWriteList();
void testWriteEmptyList();
void testWriteCheckboxListItemEndingWithCode();
void testWriteNestedBulletLists_data();
void testWriteNestedBulletLists();
void testWriteNestedNumericLists();
void testWriteTable();
void rewriteDocument_data();
void rewriteDocument();
void fromHtml_data();
void fromHtml();
private:
bool isMainFontFixed();
bool isFixedFontProportional();
QString documentToUnixMarkdown();
private:
QTextDocument *document;
};
void tst_QTextMarkdownWriter::init()
{
document = new QTextDocument();
}
void tst_QTextMarkdownWriter::cleanup()
{
delete document;
}
bool tst_QTextMarkdownWriter::isMainFontFixed()
{
bool ret = QFontInfo(QGuiApplication::font()).fixedPitch();
if (ret) {
qCWarning(lcTests) << "QFontDatabase::GeneralFont is monospaced: markdown writing is likely to use too many backticks"
<< QFontDatabase::systemFont(QFontDatabase::GeneralFont);
}
return ret;
}
bool tst_QTextMarkdownWriter::isFixedFontProportional()
{
bool ret = !QFontInfo(QFontDatabase::systemFont(QFontDatabase::FixedFont)).fixedPitch();
if (ret) {
qCWarning(lcTests) << "QFontDatabase::FixedFont is NOT monospaced: markdown writing is likely to use too few backticks"
<< QFontDatabase::systemFont(QFontDatabase::FixedFont);
}
return ret;
}
QString tst_QTextMarkdownWriter::documentToUnixMarkdown()
{
QString ret;
QTextStream ts(&ret, QIODevice::WriteOnly);
QTextMarkdownWriter writer(ts, QTextDocument::MarkdownDialectGitHub);
writer.writeAll(document);
return ret;
}
void tst_QTextMarkdownWriter::testWriteParagraph_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("expectedOutput");
QTest::newRow("empty") << "" <<
"";
QTest::newRow("spaces") << "foobar word" <<
"foobar word\n\n";
QTest::newRow("starting spaces") << " starting spaces" <<
" starting spaces\n\n";
QTest::newRow("trailing spaces") << "trailing spaces " <<
"trailing spaces \n\n";
QTest::newRow("tab") << "word\ttab x" <<
"word\ttab x\n\n";
QTest::newRow("tab2") << "word\t\ttab\tx" <<
"word\t\ttab\tx\n\n";
QTest::newRow("misc") << "foobar word\ttab x" <<
"foobar word\ttab x\n\n";
QTest::newRow("misc2") << "\t \tFoo" <<
"\t \tFoo\n\n";
}
void tst_QTextMarkdownWriter::testWriteParagraph()
{
QFETCH(QString, input);
QFETCH(QString, expectedOutput);
QTextCursor cursor(document);
cursor.insertText(input);
const QString output = documentToUnixMarkdown();
if (output != expectedOutput && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(output, expectedOutput);
}
void tst_QTextMarkdownWriter::testWriteList()
{
QTextCursor cursor(document);
QTextList *list = cursor.createList(QTextListFormat::ListDisc);
cursor.insertText("ListItem 1");
list->add(cursor.block());
cursor.insertBlock();
cursor.insertText("ListItem 2");
list->add(cursor.block());
const QString output = documentToUnixMarkdown();
const QString expected = QString::fromLatin1("- ListItem 1\n- ListItem 2\n");
if (output != expected && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(output, expected);
}
void tst_QTextMarkdownWriter::testWriteEmptyList()
{
QTextCursor cursor(document);
cursor.createList(QTextListFormat::ListDisc);
QCOMPARE(documentToUnixMarkdown(), QString::fromLatin1("- \n"));
}
void tst_QTextMarkdownWriter::testWriteCheckboxListItemEndingWithCode()
{
QTextCursor cursor(document);
QTextList *list = cursor.createList(QTextListFormat::ListDisc);
cursor.insertText("Image.originalSize property (not necessary; PdfDocument.pagePointSize() substitutes)");
list->add(cursor.block());
{
auto fmt = cursor.block().blockFormat();
fmt.setMarker(QTextBlockFormat::MarkerType::Unchecked);
cursor.setBlockFormat(fmt);
}
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 2);
cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor, 4);
QCOMPARE(cursor.selectedText(), QString::fromLatin1("PdfDocument.pagePointSize()"));
auto fmt = cursor.charFormat();
fmt.setFontFixedPitch(true);
cursor.setCharFormat(fmt);
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 5);
cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor, 4);
QCOMPARE(cursor.selectedText(), QString::fromLatin1("Image.originalSize"));
cursor.setCharFormat(fmt);
const QString output = documentToUnixMarkdown();
const QString expected = QString::fromLatin1(
"- [ ] `Image.originalSize` property (not necessary; `PdfDocument.pagePointSize()`\n substitutes)\n");
if (output != expected && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(output, expected);
}
void tst_QTextMarkdownWriter::testWriteNestedBulletLists_data()
{
QTest::addColumn<bool>("checkbox");
QTest::addColumn<bool>("checked");
QTest::addColumn<bool>("continuationLine");
QTest::addColumn<bool>("continuationParagraph");
QTest::addColumn<QString>("expectedOutput");
QTest::newRow("plain bullets") << false << false << false << false <<
"- ListItem 1\n * ListItem 2\n + ListItem 3\n- ListItem 4\n * ListItem 5\n";
QTest::newRow("bullets with continuation lines") << false << false << true << false <<
"- ListItem 1\n * ListItem 2\n + ListItem 3 with text that won't fit on one line and thus needs a\n continuation\n- ListItem 4\n * ListItem 5 with text that won't fit on one line and thus needs a\n continuation\n";
QTest::newRow("bullets with continuation paragraphs") << false << false << false << true <<
"- ListItem 1\n\n * ListItem 2\n + ListItem 3\n\n continuation\n\n- ListItem 4\n\n * ListItem 5\n\n continuation\n\n";
QTest::newRow("unchecked") << true << false << false << false <<
"- [ ] ListItem 1\n * [ ] ListItem 2\n + [ ] ListItem 3\n- [ ] ListItem 4\n * [ ] ListItem 5\n";
QTest::newRow("checked") << true << true << false << false <<
"- [x] ListItem 1\n * [x] ListItem 2\n + [x] ListItem 3\n- [x] ListItem 4\n * [x] ListItem 5\n";
QTest::newRow("checked with continuation lines") << true << true << true << false <<
"- [x] ListItem 1\n * [x] ListItem 2\n + [x] ListItem 3 with text that won't fit on one line and thus needs a\n continuation\n- [x] ListItem 4\n * [x] ListItem 5 with text that won't fit on one line and thus needs a\n continuation\n";
QTest::newRow("checked with continuation paragraphs") << true << true << false << true <<
"- [x] ListItem 1\n\n * [x] ListItem 2\n + [x] ListItem 3\n\n continuation\n\n- [x] ListItem 4\n\n * [x] ListItem 5\n\n continuation\n\n";
}
void tst_QTextMarkdownWriter::testWriteNestedBulletLists()
{
QFETCH(bool, checkbox);
QFETCH(bool, checked);
QFETCH(bool, continuationParagraph);
QFETCH(bool, continuationLine);
QFETCH(QString, expectedOutput);
QTextCursor cursor(document);
QTextBlockFormat blockFmt = cursor.blockFormat();
if (checkbox) {
blockFmt.setMarker(checked ? QTextBlockFormat::MarkerType::Checked : QTextBlockFormat::MarkerType::Unchecked);
cursor.setBlockFormat(blockFmt);
}
QTextList *list1 = cursor.createList(QTextListFormat::ListDisc);
cursor.insertText("ListItem 1");
list1->add(cursor.block());
QTextListFormat fmt2;
fmt2.setStyle(QTextListFormat::ListCircle);
fmt2.setIndent(2);
QTextList *list2 = cursor.insertList(fmt2);
cursor.insertText("ListItem 2");
QTextListFormat fmt3;
fmt3.setStyle(QTextListFormat::ListSquare);
fmt3.setIndent(3);
cursor.insertList(fmt3);
cursor.insertText(continuationLine ?
"ListItem 3 with text that won't fit on one line and thus needs a continuation" :
"ListItem 3");
if (continuationParagraph) {
QTextBlockFormat blockFmt;
blockFmt.setIndent(2);
cursor.insertBlock(blockFmt);
cursor.insertText("continuation");
}
cursor.insertBlock(blockFmt);
cursor.insertText("ListItem 4");
list1->add(cursor.block());
cursor.insertBlock();
cursor.insertText(continuationLine ?
"ListItem 5 with text that won't fit on one line and thus needs a continuation" :
"ListItem 5");
list2->add(cursor.block());
if (continuationParagraph) {
QTextBlockFormat blockFmt;
blockFmt.setIndent(2);
cursor.insertBlock(blockFmt);
cursor.insertText("continuation");
}
const QString output = documentToUnixMarkdown();
#ifdef DEBUG_WRITE_OUTPUT
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".md");
out.open(QFile::WriteOnly);
out.write(output.toUtf8());
out.close();
}
#endif
if (output != expectedOutput && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(output, expectedOutput);
}
void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
{
QTextCursor cursor(document);
QTextList *list1 = cursor.createList(QTextListFormat::ListDecimal);
cursor.insertText("ListItem 1");
list1->add(cursor.block());
QTextListFormat fmt2;
fmt2.setStyle(QTextListFormat::ListLowerAlpha);
fmt2.setNumberSuffix(QLatin1String(")"));
fmt2.setIndent(2);
QTextList *list2 = cursor.insertList(fmt2);
cursor.insertText("ListItem 2");
QTextListFormat fmt3;
fmt3.setStyle(QTextListFormat::ListDecimal);
fmt3.setIndent(3);
cursor.insertList(fmt3);
cursor.insertText("ListItem 3");
cursor.insertBlock();
cursor.insertText("ListItem 4");
list1->add(cursor.block());
cursor.insertBlock();
cursor.insertText("ListItem 5");
list2->add(cursor.block());
const QString output = documentToUnixMarkdown();
// There's no QTextList API to set the starting number so we hard-coded all lists to start at 1 (QTBUG-65384)
const QString expected = QString::fromLatin1(
"1. ListItem 1\n 1) ListItem 2\n 1. ListItem 3\n2. ListItem 4\n 2) ListItem 5\n");
if (output != expected && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(output, expected);
}
void tst_QTextMarkdownWriter::testWriteTable()
{
QTextCursor cursor(document);
QTextTable * table = cursor.insertTable(4, 3);
cursor = table->cellAt(0, 0).firstCursorPosition();
// valid Markdown tables need headers, but QTextTable doesn't make that distinction
// so QTextMarkdownWriter assumes the first row of any table is a header
cursor.insertText("one");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("two");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("three");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("alice");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("bob");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("carl");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("dennis");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("eric");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("fiona");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("gina");
/*
|one |two |three|
|------|----|-----|
|alice |bob |carl |
|dennis|eric|fiona|
|gina | | |
*/
QString md = documentToUnixMarkdown();
#ifdef DEBUG_WRITE_OUTPUT
{
QFile out("/tmp/table.md");
out.open(QFile::WriteOnly);
out.write(md.toUtf8());
out.close();
}
#endif
QString expected = QString::fromLatin1(
"\n|one |two |three|\n|------|----|-----|\n|alice |bob |carl |\n|dennis|eric|fiona|\n|gina | | |\n\n");
if (md != expected && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(md, expected);
// create table with merged cells
document->clear();
cursor = QTextCursor(document);
table = cursor.insertTable(3, 3);
table->mergeCells(0, 0, 1, 2);
table->mergeCells(1, 1, 1, 2);
cursor = table->cellAt(0, 0).firstCursorPosition();
cursor.insertText("a");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("b");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("c");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("d");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("e");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("f");
/*
+---+-+
|a |b|
+---+-+
|c| d|
+-+-+-+
|e|f| |
+-+-+-+
generates
|a ||b|
|-|-|-|
|c|d ||
|e|f| |
*/
md = documentToUnixMarkdown();
#ifdef DEBUG_WRITE_OUTPUT
{
QFile out("/tmp/table-merged-cells.md");
out.open(QFile::WriteOnly);
out.write(md.toUtf8());
out.close();
}
#endif
expected = QString::fromLatin1("\n|a ||b|\n|-|-|-|\n|c|d ||\n|e|f| |\n\n");
if (md != expected && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(md, expected);
}
void tst_QTextMarkdownWriter::rewriteDocument_data()
{
QTest::addColumn<QString>("inputFile");
QTest::newRow("block quotes") << "blockquotes.md";
QTest::newRow("example") << "example.md";
QTest::newRow("list items after headings") << "headingsAndLists.md";
QTest::newRow("word wrap") << "wordWrap.md";
QTest::newRow("links") << "links.md";
QTest::newRow("lists and code blocks") << "listsAndCodeBlocks.md";
}
void tst_QTextMarkdownWriter::rewriteDocument()
{
QFETCH(QString, inputFile);
QTextDocument doc;
QFile f(QFINDTESTDATA("data/" + inputFile));
QVERIFY(f.open(QFile::ReadOnly | QIODevice::Text));
QString orig = QString::fromUtf8(f.readAll());
f.close();
doc.setMarkdown(orig);
QString md = doc.toMarkdown();
#ifdef DEBUG_WRITE_OUTPUT
QFile out("/tmp/rewrite-" + inputFile);
out.open(QFile::WriteOnly);
out.write(md.toUtf8());
out.close();
#endif
if (md != orig && isMainFontFixed())
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(md, orig);
}
void tst_QTextMarkdownWriter::fromHtml_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("expectedOutput");
QTest::newRow("long URL") <<
"<span style=\"font-style:italic;\">https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/</span>" <<
"*https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/*\n\n";
QTest::newRow("non-emphasis inline asterisk") << "3 * 4" << "3 * 4\n\n";
QTest::newRow("arithmetic") << "(2 * a * x + b)^2 = b^2 - 4 * a * c" << "(2 * a * x + b)^2 = b^2 - 4 * a * c\n\n";
QTest::newRow("escaped asterisk after newline") <<
"The first sentence of this paragraph holds 80 characters, then there's a star. * This is wrapped, but is <em>not</em> a bullet point." <<
"The first sentence of this paragraph holds 80 characters, then there's a star.\n\\* This is wrapped, but is *not* a bullet point.\n\n";
QTest::newRow("escaped plus after newline") <<
"The first sentence of this paragraph holds 80 characters, then there's a plus. + This is wrapped, but is <em>not</em> a bullet point." <<
"The first sentence of this paragraph holds 80 characters, then there's a plus.\n\\+ This is wrapped, but is *not* a bullet point.\n\n";
QTest::newRow("escaped hyphen after newline") <<
"The first sentence of this paragraph holds 80 characters, then there's a minus. - This is wrapped, but is <em>not</em> a bullet point." <<
"The first sentence of this paragraph holds 80 characters, then there's a minus.\n\\- This is wrapped, but is *not* a bullet point.\n\n";
QTest::newRow("list items with indented continuations") <<
"<ul><li>bullet<p>continuation paragraph</p></li><li>another bullet<br/>continuation line</li></ul>" <<
"- bullet\n\n continuation paragraph\n\n- another bullet\n continuation line\n";
QTest::newRow("nested list items with continuations") <<
"<ul><li>bullet<p>continuation paragraph</p></li><li>another bullet<br/>continuation line</li><ul><li>bullet<p>continuation paragraph</p></li><li>another bullet<br/>continuation line</li></ul></ul>" <<
"- bullet\n\n continuation paragraph\n\n- another bullet\n continuation line\n\n - bullet\n\n continuation paragraph\n\n - another bullet\n continuation line\n";
QTest::newRow("nested ordered list items with continuations") <<
"<ol><li>item<p>continuation paragraph</p></li><li>another item<br/>continuation line</li><ol><li>item<p>continuation paragraph</p></li><li>another item<br/>continuation line</li></ol><li>another</li><li>another</li></ol>" <<
"1. item\n\n continuation paragraph\n\n2. another item\n continuation line\n\n 1. item\n\n continuation paragraph\n\n 2. another item\n continuation line\n\n3. another\n4. another\n";
QTest::newRow("thematic break") <<
"something<hr/>something else" <<
"something\n\n- - -\nsomething else\n\n";
QTest::newRow("block quote") <<
"<p>In 1958, Mahatma Gandhi was quoted as follows:</p><blockquote>The Earth provides enough to satisfy every man's need but not for every man's greed.</blockquote>" <<
"In 1958, Mahatma Gandhi was quoted as follows:\n\n> The Earth provides enough to satisfy every man's need but not for every man's\n> greed.\n\n";
QTest::newRow("image") <<
"<img src=\"/url\" alt=\"foo\" title=\"title\"/>" <<
"![foo](/url \"title\")\n\n";
QTest::newRow("code") <<
"<pre class=\"language-pseudocode\">\n#include \"foo.h\"\n\nblock {\n statement();\n}\n\n</pre>" <<
"```pseudocode\n#include \"foo.h\"\n\nblock {\n statement();\n}\n\n```\n\n";
// TODO
// QTest::newRow("escaped number and paren after double newline") <<
// "<p>(The first sentence of this paragraph is a line, the next paragraph has a number</p>13) but that's not part of an ordered list" <<
// "(The first sentence of this paragraph is a line, the next paragraph has a number\n\n13\\) but that's not part of an ordered list\n\n";
QTest::newRow("preformats with embedded backticks") <<
"<pre>none `one` ``two``</pre>plain<pre>```three``` ````four````</pre>plain" <<
"```\nnone `one` ``two``\n\n```\nplain\n\n```\n```three``` ````four````\n\n```\nplain\n\n";
QTest::newRow("list items with and without checkboxes") <<
"<ul><li>bullet</li><li class=\"unchecked\">unchecked item</li><li class=\"checked\">checked item</li></ul>" <<
"- bullet\n- [ ] unchecked item\n- [x] checked item\n";
}
void tst_QTextMarkdownWriter::fromHtml()
{
QFETCH(QString, input);
QFETCH(QString, expectedOutput);
document->setHtml(input);
QString output = documentToUnixMarkdown();
#ifdef DEBUG_WRITE_OUTPUT
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".md");
out.open(QFile::WriteOnly);
out.write(output.toUtf8());
out.close();
}
#endif
if (output != expectedOutput && (isMainFontFixed() || isFixedFontProportional()))
QEXPECT_FAIL("", "fixed main font or proportional fixed font (QTBUG-103484)", Continue);
QCOMPARE(output, expectedOutput);
}
QTEST_MAIN(tst_QTextMarkdownWriter)
#include "tst_qtextmarkdownwriter.moc"

View File

@ -0,0 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextobject Test:
#####################################################################
qt_internal_add_test(tst_qtextobject
SOURCES
tst_qtextobject.cpp
LIBRARIES
Qt::Gui
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qtextobject CONDITION TARGET Qt::Widgets
LIBRARIES
Qt::Widgets
)

View File

@ -0,0 +1,93 @@
// 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 <qcoreapplication.h>
#include <qdebug.h>
#include <qtextobject.h>
#include <qtextdocument.h>
#ifndef QT_NO_WIDGETS
#include <qtextedit.h>
#endif
#include <qtextcursor.h>
class tst_QTextObject : public QObject
{
Q_OBJECT
public:
tst_QTextObject();
virtual ~tst_QTextObject();
private slots:
#ifndef QT_NO_WIDGETS
void getSetCheck();
#endif
void testStandAloneTextObject();
};
tst_QTextObject::tst_QTextObject()
{
}
tst_QTextObject::~tst_QTextObject()
{
}
#ifndef QT_NO_WIDGETS
// Testing get/set functions
void tst_QTextObject::getSetCheck()
{
QTextEdit edit;
QTextFrame obj1(edit.document());
// QTextFrameLayoutData * QTextFrame::layoutData()
// void QTextFrame::setLayoutData(QTextFrameLayoutData *)
QTextFrameLayoutData *var1 = new QTextFrameLayoutData;
obj1.setLayoutData(var1);
QCOMPARE(var1, obj1.layoutData());
obj1.setLayoutData((QTextFrameLayoutData *)0);
QCOMPARE((QTextFrameLayoutData *)0, obj1.layoutData());
// delete var1; // No delete, since QTextFrame takes ownership
QTextBlock obj2 = edit.textCursor().block();
// QTextBlockUserData * QTextBlock::userData()
// void QTextBlock::setUserData(QTextBlockUserData *)
QTextBlockUserData *var2 = new QTextBlockUserData;
obj2.setUserData(var2);
QCOMPARE(var2, obj2.userData());
obj2.setUserData((QTextBlockUserData *)0);
QCOMPARE((QTextBlockUserData *)0, obj2.userData());
// int QTextBlock::userState()
// void QTextBlock::setUserState(int)
obj2.setUserState(0);
QCOMPARE(0, obj2.userState());
obj2.setUserState(INT_MIN);
QCOMPARE(INT_MIN, obj2.userState());
obj2.setUserState(INT_MAX);
QCOMPARE(INT_MAX, obj2.userState());
}
#endif
class TestTextObject : public QTextObject
{
public:
TestTextObject(QTextDocument *document) : QTextObject(document) {}
};
void tst_QTextObject::testStandAloneTextObject()
{
QTextDocument document;
TestTextObject textObject(&document);
QCOMPARE(textObject.document(), &document);
// don't crash
textObject.format();
textObject.formatIndex();
QCOMPARE(textObject.objectIndex(), -1);
}
QTEST_MAIN(tst_QTextObject)
#include "tst_qtextobject.moc"

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextodfwriter Test:
#####################################################################
qt_internal_add_test(tst_qtextodfwriter
SOURCES
tst_qtextodfwriter.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,404 @@
// 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 <QTextDocument>
#include <QTextCursor>
#include <QTextBlock>
#include <QTextList>
#include <QTextTable>
#include <QBuffer>
#include <QDebug>
#include <private/qtextodfwriter_p.h>
class tst_QTextOdfWriter : public QObject
{
Q_OBJECT
public slots:
void init();
void cleanup();
private slots:
void testWriteParagraph_data();
void testWriteParagraph();
void testWriteStyle1_data();
void testWriteStyle1();
void testWriteStyle2();
void testWriteList();
void testWriteList2();
void createArchive();
void testWriteAll();
void testWriteSection();
void testWriteTable();
void testWriteFrameFormat();
private:
/// closes the document and returns the part of the XML stream that the test wrote
QString getContentFromXml();
private:
QTextDocument *document;
QXmlStreamWriter *xmlWriter;
QTextOdfWriter *odfWriter;
QBuffer *buffer;
};
void tst_QTextOdfWriter::init()
{
document = new QTextDocument();
odfWriter = new QTextOdfWriter(*document, 0);
buffer = new QBuffer();
buffer->open(QIODevice::WriteOnly);
xmlWriter = new QXmlStreamWriter(buffer);
xmlWriter->writeNamespace(odfWriter->officeNS, "office");
xmlWriter->writeNamespace(odfWriter->textNS, "text");
xmlWriter->writeNamespace(odfWriter->styleNS, "style");
xmlWriter->writeNamespace(odfWriter->foNS, "fo");
xmlWriter->writeNamespace(odfWriter->tableNS, "table");
xmlWriter->writeStartDocument();
xmlWriter->writeStartElement("dummy");
}
void tst_QTextOdfWriter::cleanup()
{
delete document;
delete odfWriter;
delete xmlWriter;
delete buffer;
}
QString tst_QTextOdfWriter::getContentFromXml()
{
xmlWriter->writeEndDocument();
buffer->close();
QString stringContent = QString::fromUtf8(buffer->data());
QString ret;
int index = stringContent.indexOf("<dummy");
if (index > 0) {
index = stringContent.indexOf('>', index);
if (index > 0)
ret = stringContent.mid(index+1, stringContent.size() - index - 10);
}
return ret;
}
void tst_QTextOdfWriter::testWriteParagraph_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("xml");
QTest::newRow("empty") << "" <<
"<text:p text:style-name=\"p1\"/>";
QTest::newRow("spaces") << "foobar word" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">foobar <text:s text:c=\"2\"/>word</text:span></text:p>";
QTest::newRow("starting spaces") << " starting spaces" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\"><text:s text:c=\"2\"/>starting spaces</text:span></text:p>";
QTest::newRow("trailing spaces") << "trailing spaces " <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">trailing spaces <text:s/></text:span></text:p>";
QTest::newRow("tab") << "word\ttab x" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">word<text:tab/>tab x</text:span></text:p>";
QTest::newRow("tab2") << "word\t\ttab\tx" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">word<text:tab/><text:tab/>tab<text:tab/>x</text:span></text:p>";
QTest::newRow("misc") << "foobar word\ttab x" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">foobar <text:s text:c=\"2\"/>word<text:tab/>tab x</text:span></text:p>";
QTest::newRow("misc2") << "\t \tFoo" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\"><text:tab/> <text:s text:c=\"4\"/><text:tab/>Foo</text:span></text:p>";
QTest::newRow("linefeed") << (QStringLiteral("line1") + QChar(0x2028) + QStringLiteral("line2")) <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">line1<text:tab/><text:line-break/>line2</text:span></text:p>";
QTest::newRow("spaces") << "The quick brown fox jumped over the lazy dog" <<
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">The quick brown fox jumped over the lazy dog</text:span></text:p>";
}
void tst_QTextOdfWriter::testWriteParagraph()
{
QFETCH(QString, input);
QFETCH(QString, xml);
QTextCursor cursor(document);
cursor.insertText(input);
odfWriter->writeBlock(*xmlWriter, document->begin());
QCOMPARE( getContentFromXml(), xml);
}
void tst_QTextOdfWriter::testWriteStyle1_data()
{
QTest::addColumn<QString>("htmlInput");
QTest::addColumn<int>("cursorPosition");
QTest::addColumn<QString>("xml");
QString text1 = "Normal<b>bold</b><i>italic</i><b><i>Bold/Italic</i></b>";
QTest::newRow("normal") << text1 << 2 <<
"<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-family=\"Sans\"/></style:style>";
QTest::newRow("bold") << text1 << 10 <<
"<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-weight=\"bold\" fo:font-family=\"Sans\"/></style:style>";
QTest::newRow("italic") << text1 << 14 <<
"<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-style=\"italic\" fo:font-family=\"Sans\"/></style:style>";
QTest::newRow("bold+italic") << text1 << 25 <<
"<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-style=\"italic\" fo:font-weight=\"bold\" fo:font-family=\"Sans\"/></style:style>";
QString colorText = "<span style=\"color: #00FF00; background-color: #FF0000;\"> Color Text </span>";
QTest::newRow("green/red") << colorText << 3 <<
"<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-family=\"Sans\" fo:color=\"#00ff00\" fo:background-color=\"#ff0000\"/></style:style>";
}
void tst_QTextOdfWriter::testWriteStyle1()
{
QFETCH(QString, htmlInput);
QFETCH(int, cursorPosition);
QFETCH(QString, xml);
document->setHtml(htmlInput);
QTextCursor cursor(document);
cursor.setPosition(cursorPosition);
odfWriter->writeCharacterFormat(*xmlWriter, cursor.charFormat(), 4);
QCOMPARE( getContentFromXml(), xml);
}
void tst_QTextOdfWriter::testWriteStyle2()
{
QTextBlockFormat bf; // = cursor.blockFormat();
QList<QTextOption::Tab> tabs;
QTextOption::Tab tab1(40, QTextOption::RightTab);
tabs << tab1;
QTextOption::Tab tab2(80, QTextOption::DelimiterTab, 'o');
tabs << tab2;
bf.setTabPositions(tabs);
odfWriter->writeBlockFormat(*xmlWriter, bf, 1);
QString xml = QString::fromLatin1(
"<style:style style:name=\"p1\" style:family=\"paragraph\">"
"<style:paragraph-properties>"
"<style:tab-stops>"
"<style:tab-stop style:position=\"30pt\" style:type=\"right\"/>"
"<style:tab-stop style:position=\"60pt\" style:type=\"char\" style:char=\"o\"/>"
"</style:tab-stops>"
"</style:paragraph-properties>"
"</style:style>");
QCOMPARE(getContentFromXml(), xml);
}
void tst_QTextOdfWriter::testWriteList()
{
QTextCursor cursor(document);
QTextList *list = cursor.createList(QTextListFormat::ListDisc);
cursor.insertText("ListItem 1");
list->add(cursor.block());
cursor.insertBlock();
cursor.insertText("ListItem 2");
list->add(cursor.block());
odfWriter->writeBlock(*xmlWriter, cursor.block());
QString xml = QString::fromLatin1(
"<text:list text:style-name=\"L2\">"
"<text:list-item>"
//"<text:numbered-paragraph text:style-name=\"L2\" text:level=\"1\">"
//"<text:number>")+ QChar(0x25cf) + QString::fromLatin1("</text:number>" // 0x25cf is a bullet
"<text:p text:style-name=\"p3\"><text:span text:style-name=\"c0\">ListItem 2</text:span></text:p>"
"</text:list-item>"
"</text:list>");
QCOMPARE(getContentFromXml(), xml);
}
void tst_QTextOdfWriter::testWriteList2()
{
QTextCursor cursor(document);
QTextList *list = cursor.createList(QTextListFormat::ListDisc);
cursor.insertText("Cars");
list->add(cursor.block());
cursor.insertBlock();
QTextListFormat level2;
level2.setStyle(QTextListFormat::ListSquare);
level2.setIndent(2);
QTextList *list2 = cursor.createList(level2);
cursor.insertText("Model T");
list2->add(cursor.block());
cursor.insertBlock();
cursor.insertText("Kitt");
list2->add(cursor.block());
cursor.insertBlock();
cursor.insertText("Animals");
list->add(cursor.block());
cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); // start a new completely unrelated list.
QTextList *list3 = cursor.createList(QTextListFormat::ListDecimal);
cursor.insertText("Foo");
list3->add(cursor.block());
// and another block thats NOT in a list.
cursor.insertBlock(QTextBlockFormat(), QTextCharFormat());
cursor.insertText("Bar");
odfWriter->writeFrame(*xmlWriter, document->rootFrame());
QString xml = QString::fromLatin1(
"<text:list text:style-name=\"L2\">"
"<text:list-item>"
//"<text:numbered-paragraph text:style-name=\"L2\" text:level=\"1\">"
//"<text:number>")+ QChar(0x25cf) + QString::fromLatin1("</text:number>" // 0x25cf is a bullet
"<text:p text:style-name=\"p3\"><text:span text:style-name=\"c0\">Cars</text:span></text:p>"
"</text:list-item>"
"<text:list-item>"
"<text:list text:style-name=\"L4\">"
"<text:list-item>"
"<text:p text:style-name=\"p5\"><text:span text:style-name=\"c0\">Model T</text:span></text:p>"
"</text:list-item>"
"<text:list-item>"
"<text:p text:style-name=\"p5\"><text:span text:style-name=\"c0\">Kitt</text:span></text:p>"
"</text:list-item>"
"</text:list>"
"</text:list-item>"
"<text:list-item>"
"<text:p text:style-name=\"p3\"><text:span text:style-name=\"c0\">Animals</text:span></text:p>"
"</text:list-item>"
"</text:list>"
"<text:list text:style-name=\"L6\">"
"<text:list-item>"
"<text:p text:style-name=\"p7\"><text:span text:style-name=\"c0\">Foo</text:span></text:p>"
"</text:list-item>"
"</text:list>"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">Bar</text:span></text:p>");
// QString x = getContentFromXml();
// for (int i=0; i < x.length(); i+=150) qDebug() << x.mid(i, 150);
QCOMPARE(getContentFromXml(), xml);
}
void tst_QTextOdfWriter::createArchive()
{
document->setPlainText("a"); // simple doc is enough ;)
QTextOdfWriter writer(*document, buffer);
QCOMPARE(writer.createArchive(), true); // default
writer.writeAll();
/*
QFile file("createArchive-odt");
file.open(QIODevice::WriteOnly);
file.write(buffer->data());
file.close();
*/
QVERIFY(buffer->data().size() > 80);
QCOMPARE(buffer->data()[0], 'P'); // its a zip :)
QCOMPARE(buffer->data()[1], 'K');
QString mimetype(buffer->data().mid(38, 39));
QCOMPARE(mimetype, QString::fromLatin1("application/vnd.oasis.opendocument.text"));
}
void tst_QTextOdfWriter::testWriteAll()
{
document->setPlainText("a"); // simple doc is enough ;)
QTextOdfWriter writer(*document, buffer);
QCOMPARE(writer.createArchive(), true);
writer.setCreateArchive(false);
writer.writeAll();
QString result = QString(buffer->data());
// details we check elsewhere, all we have to do is check availability.
QVERIFY(result.indexOf("office:automatic-styles") >= 0);
QVERIFY(result.indexOf("<style:style style:name=\"p1\"") >= 0);
QVERIFY(result.indexOf("<style:style style:name=\"c0\"") >= 0);
QVERIFY(result.indexOf("office:body") >= 0);
QVERIFY(result.indexOf("office:text") >= 0);
QVERIFY(result.indexOf("style:style") >= 0);
}
void tst_QTextOdfWriter::testWriteSection()
{
QTextCursor cursor(document);
cursor.insertText("foo\nBar");
QTextFrameFormat ff;
cursor.insertFrame(ff);
cursor.insertText("baz");
odfWriter->writeFrame(*xmlWriter, document->rootFrame());
QString xml = QString::fromLatin1(
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">foo</text:span></text:p>"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">Bar</text:span></text:p>"
"<text:section>"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">baz</text:span></text:p>"
"</text:section>"
"<text:p text:style-name=\"p1\"/>");
QCOMPARE(getContentFromXml(), xml);
}
void tst_QTextOdfWriter::testWriteTable()
{
// create table with merged cells
QTextCursor cursor(document);
QTextTable * table = cursor.insertTable(3, 3);
table->mergeCells(1, 0, 2, 2);
table->mergeCells(0, 1, 1, 2);
cursor = table->cellAt(0, 0).firstCursorPosition();
cursor.insertText("a");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("b");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("c");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("d");
cursor.movePosition(QTextCursor::NextCell);
cursor.insertText("e");
/*
+-+---+
|a|b |
+-+-+-+
|c |d|
+ +-+
| |e|
+-+-+-+
*/
odfWriter->writeFrame(*xmlWriter, document->rootFrame());
QString xml = QString::fromLatin1(
"<text:p text:style-name=\"p1\"/>"
"<table:table table:style-name=\"Table2\">"
"<table:table-column table:number-columns-repeated=\"3\"/>"
"<table:table-row>"
"<table:table-cell table:style-name=\"T3\">"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">a</text:span></text:p>"
"</table:table-cell>"
"<table:table-cell table:number-columns-spanned=\"2\" table:style-name=\"T6\">"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c7\">b</text:span></text:p>"
"</table:table-cell>"
"</table:table-row>"
"<table:table-row>"
"<table:table-cell table:number-columns-spanned=\"2\" table:number-rows-spanned=\"2\" table:style-name=\"T5\">"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c8\">c</text:span></text:p>"
"</table:table-cell>"
"<table:table-cell table:style-name=\"T3\">"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">d</text:span></text:p>"
"</table:table-cell>"
"</table:table-row>"
"<table:table-row>"
"<table:table-cell table:style-name=\"T3\">"
"<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">e</text:span></text:p>"
"</table:table-cell>"
"</table:table-row>"
"</table:table>"
"<text:p text:style-name=\"p1\"/>");
QCOMPARE(getContentFromXml(), xml);
}
void tst_QTextOdfWriter::testWriteFrameFormat()
{
QTextFrameFormat tff;
tff.setTopMargin(20);
tff.setBottomMargin(20);
tff.setLeftMargin(20);
tff.setRightMargin(20);
QTextCursor tc(document);
odfWriter->writeFrameFormat(*xmlWriter, tff, 0);
// Value of 15pt is based on the pixelToPoint() calculation done in qtextodfwriter.cpp
QString xml = QString::fromLatin1(
"<style:style style:name=\"s0\" style:family=\"section\">"
"<style:section-properties fo:margin-top=\"15pt\" fo:margin-bottom=\"15pt\""
" fo:margin-left=\"15pt\" fo:margin-right=\"15pt\"/>"
"</style:style>");
QCOMPARE(getContentFromXml(), xml);
}
QTEST_MAIN(tst_QTextOdfWriter)
#include "tst_qtextodfwriter.moc"

View File

@ -0,0 +1,23 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(WIN32)
return()
endif()
if(NOT QT_FEATURE_private_tests)
return()
endif()
#####################################################################
## tst_qtextpiecetable Test:
#####################################################################
qt_internal_add_test(tst_qtextpiecetable
SOURCES
../qtextdocument/common.h
tst_qtextpiecetable.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtextscriptengine Test:
#####################################################################
qt_internal_add_test(tst_qtextscriptengine
SOURCES
tst_qtextscriptengine.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)

View File

@ -0,0 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## generate Binary:
#####################################################################
qt_internal_add_executable(generate
GUI
SOURCES
main.cpp
INCLUDE_DIRECTORIES
/usr/include/freetype2
LIBRARIES
Qt::CorePrivate
Qt::Gui
)

View File

@ -0,0 +1,91 @@
// 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 <QApplication>
#include <QTextEdit>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QFontDialog>
#include <QPushButton>
#define private public
#include <qfont.h>
#include <private/qtextengine_p.h>
#include <private/qfontengine_p.h>
#include <qtextlayout.h>
#undef private
class MyEdit : public QTextEdit {
Q_OBJECT
public:
MyEdit(QWidget *p) : QTextEdit(p) { setReadOnly(true); }
public slots:
void setText(const QString &str);
void changeFont();
public:
QLineEdit *lineEdit;
};
void MyEdit::setText(const QString &str)
{
if (str.isEmpty()) {
clear();
return;
}
QTextLayout layout(str, lineEdit->font());
QTextEngine *e = layout.d;
e->itemize();
e->shape(0);
QString result;
result = "Using font '" + e->fontEngine(e->layoutData->items[0])->fontDef.family + "'\n\n";
result += "{ { ";
for (int i = 0; i < str.length(); ++i)
result += "0x" + QString::number(str.at(i).unicode(), 16) + ", ";
result += "0x0 },\n { ";
for (int i = 0; i < e->layoutData->items[0].num_glyphs; ++i)
result += "0x" + QString::number(e->layoutData->glyphLayout.glyphs[i], 16) + ", ";
result += "0x0 } }";
setPlainText(result);
}
void MyEdit::changeFont()
{
bool ok;
QFont f = QFontDialog::getFont(&ok, lineEdit->font(), topLevelWidget());
if (ok)
lineEdit->setFont(f);
}
int main(int argc, char **argv)
{
QApplication a(argc, argv);
QWidget *mw = new QWidget(0);
QVBoxLayout *l = new QVBoxLayout(mw);
QLineEdit *le = new QLineEdit(mw);
l->addWidget(le);
MyEdit *view = new MyEdit(mw);
view->lineEdit = le;
l->addWidget(view);
QPushButton *button = new QPushButton("Change Font", mw);
l->addWidget(button);
QObject::connect(le, SIGNAL(textChanged(QString)), view, SLOT(setText(QString)));
QObject::connect(button, SIGNAL(clicked()), view, SLOT(changeFont()));
mw->resize(500, 300);
mw->show();
return a.exec();
}
#include <main.moc>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qtexttable Test:
#####################################################################
qt_internal_add_test(tst_qtexttable
SOURCES
tst_qtexttable.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qtexttable CONDITION TARGET Qt::Widgets
LIBRARIES
Qt::Widgets
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qzip Test:
#####################################################################
# Collect test data
file(GLOB_RECURSE test_data
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
testdata/*
)
qt_internal_add_test(tst_qzip
SOURCES
tst_qzip.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
TESTDATA ${test_data}
)

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More