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,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(glyphshaping)
add_subdirectory(textperformance)
add_subdirectory(nativetext)

View File

@ -0,0 +1,37 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(codeeditor LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/widgets/codeeditor")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(codeeditor
codeeditor.cpp codeeditor.h
main.cpp
)
set_target_properties(codeeditor PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(codeeditor PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
install(TARGETS codeeditor
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -0,0 +1,134 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "codeeditor.h"
#include <QPainter>
#include <QTextBlock>
//![constructor]
CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent)
{
lineNumberArea = new LineNumberArea(this);
connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth);
connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea);
connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
updateLineNumberAreaWidth(0);
highlightCurrentLine();
}
//![constructor]
//![extraAreaWidth]
int CodeEditor::lineNumberAreaWidth()
{
int digits = 1;
int max = qMax(1, blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
return space;
}
//![extraAreaWidth]
//![slotUpdateExtraAreaWidth]
void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
//![slotUpdateExtraAreaWidth]
//![slotUpdateRequest]
void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
{
if (dy)
lineNumberArea->scroll(0, dy);
else
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
if (rect.contains(viewport()->rect()))
updateLineNumberAreaWidth(0);
}
//![slotUpdateRequest]
//![resizeEvent]
void CodeEditor::resizeEvent(QResizeEvent *e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
//![resizeEvent]
//![cursorPositionChanged]
void CodeEditor::highlightCurrentLine()
{
QList<QTextEdit::ExtraSelection> extraSelections;
if (!isReadOnly()) {
QTextEdit::ExtraSelection selection;
QColor lineColor = QColor(Qt::yellow).lighter(160);
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
setExtraSelections(extraSelections);
}
//![cursorPositionChanged]
//![extraAreaPaintEvent_0]
void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
//![extraAreaPaintEvent_0]
//![extraAreaPaintEvent_1]
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
int bottom = top + qRound(blockBoundingRect(block).height());
//![extraAreaPaintEvent_1]
//![extraAreaPaintEvent_2]
while (block.isValid() && top <= event->rect().bottom()) {
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
painter.setPen(Qt::black);
painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + qRound(blockBoundingRect(block).height());
++blockNumber;
}
}
//![extraAreaPaintEvent_2]

View File

@ -0,0 +1,68 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef CODEEDITOR_H
#define CODEEDITOR_H
#include <QPlainTextEdit>
QT_BEGIN_NAMESPACE
class QPaintEvent;
class QResizeEvent;
class QSize;
class QWidget;
QT_END_NAMESPACE
class LineNumberArea;
//![codeeditordefinition]
class CodeEditor : public QPlainTextEdit
{
Q_OBJECT
public:
CodeEditor(QWidget *parent = nullptr);
void lineNumberAreaPaintEvent(QPaintEvent *event);
int lineNumberAreaWidth();
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void highlightCurrentLine();
void updateLineNumberArea(const QRect &rect, int dy);
private:
QWidget *lineNumberArea;
};
//![codeeditordefinition]
//![extraarea]
class LineNumberArea : public QWidget
{
public:
LineNumberArea(CodeEditor *editor) : QWidget(editor), codeEditor(editor)
{}
QSize sizeHint() const override
{
return QSize(codeEditor->lineNumberAreaWidth(), 0);
}
protected:
void paintEvent(QPaintEvent *event) override
{
codeEditor->lineNumberAreaPaintEvent(event);
}
private:
CodeEditor *codeEditor;
};
//![extraarea]
#endif

View File

@ -0,0 +1,8 @@
QT += widgets
HEADERS = codeeditor.h
SOURCES = main.cpp \
codeeditor.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/widgets/codeeditor
INSTALLS += target

View File

@ -0,0 +1,173 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example widgets/codeeditor
\title Code Editor Example
\ingroup examples-widgets
\brief The Code Editor example shows how to create a simple editor that
has line numbers and that highlights the current line.
\borderedimage codeeditor-example.png
As can be seen from the image, the editor displays the line
numbers in an area to the left of the area for editing. The editor
will highlight the line containing the cursor.
We implement the editor in \c CodeEditor, which is a widget that
inherits QPlainTextEdit. We keep a separate widget in \c
CodeEditor (\c LineNumberArea) onto which we draw the line
numbers.
QPlainTextEdit inherits from QAbstractScrollArea, and editing
takes place within its \l{QAbstractScrollArea::}{viewport()}'s
margins. We make room for our line number area by setting the left
margin of the viewport to the size we need to draw the line
numbers.
When it comes to editing code, we prefer QPlainTextEdit over
QTextEdit because it is optimized for handling plain text. See
the QPlainTextEdit class description for details.
QPlainTextEdit lets us add selections in addition to the
selection the user can make with the mouse or keyboard. We use
this functionality to highlight the current line. More on this
later.
We will now move on to the definitions and implementations of \c
CodeEditor and \c LineNumberArea. Let's start with the \c
LineNumberArea class.
\section1 The LineNumberArea Class
We paint the line numbers on this widget, and place it over the \c
CodeEditor's \l{QAbstractScrollArea::}{viewport()}'s left margin
area.
We need to use protected functions in QPlainTextEdit while
painting the area. So to keep things simple, we paint the area in
the \c CodeEditor class. The area also asks the editor to
calculate its size hint.
Note that we could simply paint the line numbers directly on the
code editor, and drop the LineNumberArea class. However, the
QWidget class helps us to \l{QWidget::}{scroll()} its contents.
Also, having a separate widget is the right choice if we wish to
extend the editor with breakpoints or other code editor features.
The widget would then help in the handling of mouse events.
\snippet widgets/codeeditor/codeeditor.h extraarea
\section1 CodeEditor Class Definition
Here is the code editor's class definition:
\snippet widgets/codeeditor/codeeditor.h codeeditordefinition
In the editor we resize and draw the line numbers on the \c
LineNumberArea. We need to do this when the number of lines in the
editor changes, and when the editor's viewport() is scrolled. Of
course, it is also done when the editor's size changes. We do
this in \c updateLineNumberWidth() and \c updateLineNumberArea().
Whenever, the cursor's position changes, we highlight the current
line in \c highlightCurrentLine().
\section1 CodeEditor Class Implementation
We will now go through the code editors implementation, starting
off with the constructor.
\snippet widgets/codeeditor/codeeditor.cpp constructor
In the constructor we connect our slots to signals in
QPlainTextEdit. It is necessary to calculate the line number area
width and highlight the first line when the editor is created.
\snippet widgets/codeeditor/codeeditor.cpp extraAreaWidth
The \c lineNumberAreaWidth() function calculates the width of the
\c LineNumberArea widget. We take the number of digits in the last
line of the editor and multiply that with the maximum width of a
digit.
\snippet widgets/codeeditor/codeeditor.cpp slotUpdateExtraAreaWidth
When we update the width of the line number area, we simply call
QAbstractScrollArea::setViewportMargins().
\snippet widgets/codeeditor/codeeditor.cpp slotUpdateRequest
This slot is invoked when the editors viewport has been scrolled.
The QRect given as argument is the part of the editing area that
is do be updated (redrawn). \c dy holds the number of pixels the
view has been scrolled vertically.
\snippet widgets/codeeditor/codeeditor.cpp resizeEvent
When the size of the editor changes, we also need to resize the
line number area.
\snippet widgets/codeeditor/codeeditor.cpp cursorPositionChanged
When the cursor position changes, we highlight the current line,
i.e., the line containing the cursor.
QPlainTextEdit gives the possibility to have more than one
selection at the same time. we can set the character format
(QTextCharFormat) of these selections. We clear the cursors
selection before setting the new new
QPlainTextEdit::ExtraSelection, else several lines would get
highlighted when the user selects multiple lines with the mouse.
\omit ask someone how this works \endomit
One sets the selection with a text cursor. When using the
FullWidthSelection property, the current cursor text block (line)
will be selected. If you want to select just a portion of the text
block, the cursor should be moved with QTextCursor::movePosition()
from a position set with \l{QTextCursor::}{setPosition()}.
\snippet widgets/codeeditor/codeeditor.cpp extraAreaPaintEvent_0
The \c lineNumberAreaPaintEvent() is called from \c LineNumberArea
whenever it receives a paint event. We start off by painting the
widget's background.
\snippet widgets/codeeditor/codeeditor.cpp extraAreaPaintEvent_1
We will now loop through all visible lines and paint the line
numbers in the extra area for each line. Notice that in a plain
text edit each line will consist of one QTextBlock; though, if
line wrapping is enabled, a line may span several rows in the text
edit's viewport.
We get the top and bottom y-coordinate of the first text block,
and adjust these values by the height of the current text block in
each iteration in the loop.
\snippet widgets/codeeditor/codeeditor.cpp extraAreaPaintEvent_2
Notice that we check if the block is visible in addition to check
if it is in the areas viewport - a block can, for example, be
hidden by a window placed over the text edit.
\section1 Suggestions for Extending the Code Editor
No self-respecting code editor is without a syntax
highligther; the \l{Syntax Highlighter Example} shows how to
create one.
In addition to line numbers, you can add more to the extra area,
for instance, break points.
QSyntaxHighlighter gives the possibility to add user data to each
text block with
\l{QSyntaxHighlighter::}{setCurrentBlockUserData()}. This can be
used to implement parenthesis matching. In the \c
highlightCurrentLine(), the data of the currentBlock() can be
fetched with QTextBlock::userData(). Matching parentheses can be
highlighted with an extra selection. The "Matching Parentheses
with QSyntaxHighlighter" article in Qt Quarterly 31 implements
this. You find it here: \l{http://doc.qt.io/archives/qq/}.
*/

View File

@ -0,0 +1,18 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QApplication>
#include "codeeditor.h"
int main(int argv, char **args)
{
QApplication app(argv, args);
CodeEditor editor;
editor.setWindowTitle(QObject::tr("Code Editor Example"));
editor.show();
return app.exec();
}

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## glyphshaping Binary:
#####################################################################
qt_internal_add_manual_test(glyphshaping
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Gui
Qt::Widgets
)

View File

@ -0,0 +1,5 @@
QT += widgets
SOURCES = main.cpp
glyphshaping_data.path = .
glyphshaping_data.files = $$PWD/glyphshaping_data.xml
DEPLOYMENT += glyphshaping_data

View File

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<shapingtests>
<language name="Vietnamese">
<test
name="capital U, combining horn"
inpututf16="0x0055, 0x031B"
outpututf16="0x01AF"
/>
<test
name="capital U, apostrophe"
inpututf16="0x0055, 0x0027"
outpututf16="0x0055"
/>
<test
name="capital U, modifier prime"
inpututf16="0x0055, 0x02B9"
outpututf16="0x0055"
/>
<test
name="capital U, modifier apostrophe"
inpututf16="0x0055, 0x02BC"
outpututf16="0x0055"
/>
<test
name="capital U, combining comma above right"
inpututf16="0x0055, 0x0315"
outpututf16="0x0055, 0x0315"
/>
<test
name="capital U, right single quote mark"
inpututf16="0x0055, 0x2019"
outpututf16="0x0055"
/>
<test
name="capital U with horn, space"
inpututf16="0x01AF, 0x0020"
outpututf16="0x01AF"
/>
<test
name="capital E, combining horn"
inpututf16="0x0045, 0x031B"
outpututf16="0x0045, 0x031B"
/>
<test
name="capital A, combining breve, combining acute"
inpututf16="0x0041, 0x0306, 0x0301"
outpututf16="0x1EAE"
/>
<test
name="capital A with breve, combining acute"
inpututf16="0x0102, 0x0301"
outpututf16="0x1EAE"
/>
<test
name="capital A, combining acute, combining breve"
inpututf16="0x0041, 0x0301, 0x0306"
outpututf16="0x0041, 0x0301, 0x0306"
/>
<test
name="capital A, combining dot below, combining breve"
inpututf16="0x0041, 0x0323"
outpututf16="0x0306"
/>
<test
name="capital A with dot below, combining breve"
inpututf16="0x1EA0, 0x0306"
outpututf16="0x1EB6"
/>
<test
name="capital A with breve, combining dot below"
inpututf16="0x0102, 0x0323"
outpututf16="0x0102, 0x0323"
/>
<test
name="capital E, combining circumflex, combining acute"
inpututf16="0x0045, 0x0302, 0x0301"
outpututf16="0x1EBE"
/>
<test
name="capital E with circumflex, combining acute"
inpututf16="0x00CA, 0x0301"
outpututf16="0x1EBE"
/>
<test
name="capital O, combining horn, combining hook above"
inpututf16="0x004F, 0x031B, 0x0309"
outpututf16="0x1EDE"
/>
<test
name="capital O with horn, combining hook above"
inpututf16="0x01A0, 0x0309"
outpututf16="0x1EDE"
/>
</language>
<language name="Tamil">
<test
name="Tamil Ka"
inpututf16="0x0B95"
outputglyphids="0x0bf6"
/>
<test
name="Tamil e"
inpututf16="0x0B8E"
outputglyphids="0x0bf0"
/>
<test
name="Latin A"
inpututf16="0x0061"
outpututf16="0x0061"
/>
<test
name="Hindi Ka"
inpututf16="0x0905"
outputglyphids="0x0528"
/>
<test
name="03 - 1: Latin 06"
inpututf16="0x0036"
outputglyphids="0x077A"
/>
<test
name="03 - 2: Tamil 06"
inpututf16="0x0BEC"
outputglyphids="0x0c20"
/>
<test
name="10 1.3.6 - 1: Pa, Virama, Ka, Virama, Tta, -e"
inpututf16="0x0BAA, 0x0BCD, 0x0B95, 0x0BCD, 0x0B9F, 0x0BC7"
outputglyphids="0x0c3a, 0x0c30, 0x0c13, 0x0bfb"
/>
<test
name="10 1.3.6 - 2: Pa, Virama, Ka, AU"
inpututf16="0x0BAA, 0x0BCD, 0x0B95, 0x0BCC"
outputglyphids="0x0c3a, 0x0c12, 0x0bf6, 0x0c19"
/>
<test
name="10 1.3.6 - 3: Ka, Virama, Ssa, OO"
inpututf16="0x0B95, 0x0BCD, 0x0BB7, 0x0BCB"
outputglyphids="0x0c13, 0x0c2f, 0x0c0d"
/>
<test
name="11: Ka, -e"
inpututf16="0x0B95, 0x0BC7"
outputglyphids="0x0c13, 0x0bf6"
/>
<test
name="12 1.3.5.2: Ka, O"
inpututf16="0x0B95, 0x0BCA"
outputglyphids="0x0c12, 0x0bf6, 0x0c0d"
/>
<test
name="13 - 1: Ka"
inpututf16="0x0B95"
outputglyphids="0x0bf6"
/>
<test
name="13 - 2: Aythem, A"
inpututf16="0x0B83, 0x0B85"
/>
<test
name="14 - 1: Ka, Anusvara"
inpututf16="0x0B95, 0x0B82"
outputglyphids="0x0bf6, 0x0be8"
/>
<test
name="14 - 2: Ka"
inpututf16="0x0B95"
outputglyphids="0x0bf6"
/>
<test
name="15 - 1: Ra, Virama"
inpututf16="0x0BB0, 0x0BCD"
outputglyphids="0x0c03"
/>
<test
name="15 - 2: ZWJ"
inpututf16="0x8205"
/>
<test
name="16: Ka, Anusvara"
inpututf16="0x0B95, 0x0B82"
outputglyphids="0x0bf6"
/>
<test
name="17 1.3.11 - 1: Tta, I"
inpututf16="0x0B9F, 0x0BBF"
outputglyphids="0x0c51"
/>
<test
name="17 1.3.11 - 2: Tta, Ii"
inpututf16="0x0B9F, 0x0BC0"
outputglyphids="0x0c52"
/>
<test
name="18 - 1: Ra, I"
inpututf16="0x0BB0, 0x0BBF"
outputglyphids="0x0c0d, 0x0c0e"
/>
<test
name="18 - 2: Ra, Ii"
inpututf16="0x0BB0, 0x0BC0"
outputglyphids="0x0c0d, 0x0c0f"
/>
<test
name="19 - 1: Nga, I"
inpututf16="0x0B99, 0x0BBF"
outputglyphids="0x0bf7, 0x0c0e"
/>
<test
name="19 - 2: Nga, Ii"
inpututf16="0x0B99, 0x0BC0"
outputglyphids="0x0c4a"
/>
<test
name="20 - 1: Ja, U"
inpututf16="0x0B9C, 0x0BC1"
outputglyphids="0x0bf9, 0x0c10"
/>
<test
name="20 - 2: Ja, Uu"
inpututf16="0x0B9C, 0x0BC2"
outputglyphids="0x0bf9, 0x0c11"
/>
<test
name="21 1.3.15: Ka, Ai"
inpututf16="0x0B95, 0x0BC8"
outputglyphids="0x0c14, 0x0bf6"
/>
<test
name="22: Ka, Virama, Ssa"
inpututf16="0x0B95, 0x0BCD, 0x0BB7"
outputglyphids="0x0c2f"
/>
<test
name="23 1.3.17: Sa, Virama, Ra, Matra I"
inpututf16="0x0BB8, 0x0BCD, 0x0BB0, 0x0BC0"
outputglyphids="0x0c79"
/>
<test
name="24 1.3.18 - 1: Ka"
inpututf16="0x0B95"
outputglyphids="0x0bf6"
/>
<test
name="24 1.3.18 - 2: Virama, ZWJ"
inpututf16="0x0BCD, 0x8205"
/>
</language>
</shapingtests>

View File

@ -0,0 +1,235 @@
// 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 <QDir>
#include <QFile>
#include <QFontMetrics>
#include <QImage>
#include <QList>
#include <QPainter>
#include <QStringList>
#include <QXmlStreamReader>
static const int fontPixelSize = 25;
static const QLatin1String fontFamily("Series 60 Sans");
struct testDataSet
{
QString language;
QString name;
QString input;
QString inputOriginal;
QString output;
QString outputOriginal;
QList<uint> outputGlyphIDs;
QString outputGlyphIDsOriginal;
};
QString charHexCsv2String(const QString &csv)
{
QString result;
foreach (const QString &charString, csv.split(QLatin1Char(','), Qt::SkipEmptyParts)) {
bool isOk;
const uint charUInt = charString.toUInt(&isOk, 16);
Q_ASSERT(isOk);
const int size = charUInt >= SHRT_MAX ? 2:1;
result.append(QString::fromUtf16((const ushort*)&charUInt, size));
}
return result;
}
QList<testDataSet> testDataSetList()
{
QList<testDataSet> result;
QFile file("glyphshaping_data.xml");
const bool success = file.open(QIODevice::ReadOnly);
Q_ASSERT(success);
const QLatin1String language("language");
const QLatin1String test("test");
const QLatin1String inputUtf16("inpututf16");
const QLatin1String outputUtf16("outpututf16");
const QLatin1String outputGlyphIDs("outputglyphids");
const QLatin1String name("name");
QString languageName;
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
const QXmlStreamReader::TokenType token = reader.readNext();
switch (token) {
case QXmlStreamReader::StartElement:
if (reader.name() == language) {
Q_ASSERT(reader.attributes().hasAttribute(name));
languageName = reader.attributes().value(name).toString();
} else if (reader.name() == test) {
if (!reader.attributes().hasAttribute(outputUtf16)
&& !reader.attributes().hasAttribute(outputGlyphIDs))
continue;
Q_ASSERT(!languageName.isEmpty());
Q_ASSERT(reader.attributes().hasAttribute(name));
Q_ASSERT(reader.attributes().hasAttribute(inputUtf16));
testDataSet set;
set.language = languageName;
set.name = reader.attributes().value(name).toString();
set.inputOriginal = reader.attributes().value(inputUtf16).toString();
set.input = charHexCsv2String(set.inputOriginal);
set.outputOriginal = reader.attributes().value(outputUtf16).toString();
set.output = charHexCsv2String(set.outputOriginal);
set.outputGlyphIDsOriginal = reader.attributes().value(outputGlyphIDs).toString();
result.append(set);
}
break;
default:
break;
}
}
return result;
}
QImage renderedText(const QString &text, const QFont &font)
{
const QFontMetrics metrics(font);
const QRect boundingRect = metrics.boundingRect(text);
QImage result(boundingRect.size(), QImage::Format_ARGB32);
result.fill(0);
QPainter p(&result);
p.setFont(font);
p.drawText(boundingRect.translated(-boundingRect.topLeft()), text);
return result;
}
QString dumpImageHtml(const QString &text, const QString &pathName)
{
if (text.isEmpty())
return QLatin1String("<td/>");
QFont font(fontFamily);
font.setPixelSize(fontPixelSize);
const QImage textImage = renderedText(text, font);
const QString imageFileName =
(pathName + QDir::separator() + QLatin1String("%1.png"))
.arg(textImage.cacheKey());
const bool success = textImage.save(imageFileName);
Q_ASSERT(success);
return
QString::fromLatin1("<td title=\"%2\"><img src=\"%1\" alt=\"%2\" width=\"%3\" height=\"%4\"/></td>")
.arg(QDir::cleanPath(imageFileName)).arg(text).arg(textImage.width()).arg(textImage.height());
}
QString dlItem(const QString &dt, const QString &dd)
{
if (!dd.trimmed().isEmpty())
return QString::fromLatin1("\t\t\t\t\t\t<dt>%1</dt><dd>%2</dd>\n").arg(dt).arg(dd);
return QString();
}
bool dumpHtml(const QString &pathName)
{
QFile htmlPage(pathName + QDir::separator() + QLatin1String("index.html"));
if (!htmlPage.open(QFile::WriteOnly))
return false;
QString platformName = QString::fromLatin1(
#if defined(Q_OS_WIN)
"Win32"
#else
""
#endif
);
QString result = QString::fromLatin1(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
"\t<head>\n"
"\t\t<title>Qt on %1 glyph shaping (%2)</title>\n"
"\t\t<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />\n"
"\t\t<style type=\"text/css\" media=\"screen\">\n"
"\t\t\ttable { font-family: Arial; background-color: #ccccff; font-size: 12pt; }\n"
"\t\t\ttd { font-family:\"%2\"; background-color: #eeeeee; font-size: %3px; }\n"
"\t\t\tth { font-weight:normal; }\n"
"\t\t\tdl { font-family: Arial; font-size: 8pt; margin: 3px; }\n"
"\t\t\tdt { font-weight: bold; float: left; }\n"
"\t\t\ttr:hover { background-color: #ddddff; }\n"
"\t\t\ttd:hover { background-color: #ddddff; }\n"
"\t\t</style>\n"
"\t</head>\n"
"\t<body>\n"
"\t\t<h1>Qt on %1 glyph shaping (%2)</h1>\n"
"\t\t<dl>\n"
"\t\t\t<dt>I</dt><dd>Input Utf-16 to shaper</dd>\n"
"\t\t\t<dt>O-Utf</dt><dd>expected output Utf-16</dd>\n"
"\t\t\t<dt>O-ID</dt><dd>expected output Glyph IDs for \"Series 60 Sans\"</dd>\n"
"\t\t</dl>\n"
"\t\t<table>\n"
).arg(platformName).arg(fontFamily).arg(fontPixelSize);
QString languageName;
foreach (const testDataSet &dataSet, testDataSetList()) {
if (languageName != dataSet.language) {
result.append(QString::fromLatin1(
"\t\t\t<tr>\n"
"\t\t\t\t<th rowspan=\"2\"><h2>%1</h2></th>\n"
"\t\t\t\t<th colspan=\"2\">Qt/%2</th>\n"
"\t\t\t\t<th rowspan=\"2\">Glyphs</th>\n"
"\t\t\t\t<th colspan=\"2\">Browser</th>\n"
"\t\t\t</tr>\n"
"\t\t\t<tr>\n"
"\t\t\t\t<th>In</th>\n"
"\t\t\t\t<th>Out</th>\n"
"\t\t\t\t<th>In</th>\n"
"\t\t\t\t<th>Out</th>\n"
"\t\t\t</tr>\n"
).arg(dataSet.language).arg(platformName));
languageName = dataSet.language;
}
QString glyphsData;
if (!dataSet.inputOriginal.isEmpty())
glyphsData.append(dlItem(QLatin1String("I"), dataSet.inputOriginal));
if (!dataSet.outputOriginal.isEmpty())
glyphsData.append(dlItem(QLatin1String("O-Utf"), dataSet.outputOriginal));
if (!dataSet.outputGlyphIDsOriginal.isEmpty())
glyphsData.append(dlItem(QLatin1String("O-ID"), dataSet.outputGlyphIDsOriginal));
if (!glyphsData.isEmpty()) {
glyphsData.prepend(QLatin1String("\t\t\t\t\t<dl>\n"));
glyphsData.append(QLatin1String("\t\t\t\t\t</dl>\n"));
}
result.append(QString::fromLatin1(
"\t\t\t<tr>\n"
"\t\t\t\t<th>%1</th>\n"
"\t\t\t\t%2\n"
"\t\t\t\t%3\n"
"\t\t\t\t<td>\n"
"%4"
"\t\t\t\t</td>\n"
"\t\t\t\t<td>%5</td>\n"
"\t\t\t\t<td>%6</td>\n"
"\t\t\t</tr>\n"
).arg(dataSet.name)
.arg(dumpImageHtml(dataSet.input, pathName))
.arg(dumpImageHtml(dataSet.output, pathName))
.arg(glyphsData)
.arg(dataSet.input)
.arg(dataSet.output)
);
}
result.append(QString::fromLatin1(
"\t\t</table>\n"
"\t</body>\n"
"</html>")
);
htmlPage.write(result.toUtf8());
return true;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
return dumpHtml(QLatin1String(".")) ? 0 : 1;
}

View File

@ -0,0 +1,29 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## nativetext Binary:
#####################################################################
qt_internal_add_manual_test(nativetext
SOURCES
main.cpp
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
Qt::Widgets
)
## Scopes:
#####################################################################
qt_internal_extend_target(nativetext CONDITION APPLE
LIBRARIES
${FWAppKit}
${FWCoreGraphics}
${FWFoundation}
COMPILE_OPTIONS
-x
objective-c++
)

View File

@ -0,0 +1,295 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtWidgets>
#ifdef Q_OS_DARWIN
#include <private/qcoregraphics_p.h>
#include <private/qcore_mac_p.h>
#include <Foundation/Foundation.h>
#include <private/qfont_p.h>
#include <private/qfontengine_p.h>
#endif
static int s_mode;
static QString s_text = QString::fromUtf8("The quick brown \xF0\x9F\xA6\x8A jumps over the lazy \xF0\x9F\x90\xB6");
class TextRenderer : public QWidget
{
Q_OBJECT
public:
enum RenderingMode { QtRendering, NativeRendering };
Q_ENUM(RenderingMode);
TextRenderer(qreal pointSize, const QString &text, const QColor &textColor = QColor(), const QColor &bgColor = QColor())
: m_text(text)
{
if (pointSize) {
QFont f = font();
f.setPointSize(pointSize);
setFont(f);
}
if (textColor.isValid()) {
QPalette p = palette();
p.setColor(QPalette::Text, textColor);
setPalette(p);
}
if (bgColor.isValid()) {
QPalette p = palette();
p.setColor(QPalette::Window, bgColor);
setPalette(p);
}
}
QString text() const
{
return !m_text.isNull() ? m_text : s_text;
}
QSize sizeHint() const override
{
QFontMetrics fm = fontMetrics();
return QSize(fm.boundingRect(text()).width(), fm.height());
}
bool event(QEvent * event) override
{
if (event->type() == QEvent::ToolTip) {
QString toolTip;
QDebug debug(&toolTip);
debug << "textColor =" << palette().color(QPalette::Text) << "bgColor =" << palette().color(QPalette::Window);
setToolTip(toolTip);
}
return QWidget::event(event);
}
void paintEvent(QPaintEvent *) override
{
QImage image(size() * devicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(devicePixelRatio());
QPainter p(&image);
p.fillRect(QRect(0, 0, image.width(), image.height()), palette().window().color());
const int ascent = fontMetrics().ascent();
QPen metricsPen(QColor(112, 216, 255), 1.0);
metricsPen.setCosmetic(true);
p.setPen(metricsPen);
p.drawLine(QPoint(0, ascent), QPoint(width(), ascent));
p.end();
if (s_mode == QtRendering)
renderQtText(image);
else
renderNativeText(image);
QPainter wp(this);
wp.drawImage(QPoint(0, 0), image);
}
void renderQtText(QImage &image)
{
QPainter p(&image);
const int ascent = fontMetrics().ascent();
p.setPen(palette().text().color());
QFont f = font();
f.setResolveMask(-1);
p.setFont(f);
p.drawText(QPoint(0, ascent), text());
}
void renderNativeText(QImage &image)
{
#ifdef Q_OS_DARWIN
QMacAutoReleasePool pool;
QMacCGContext ctx(&image);
const auto *fontEngine = QFontPrivate::get(font())->engineForScript(QChar::Script_Common);
Q_ASSERT(fontEngine);
if (fontEngine->type() == QFontEngine::Multi) {
fontEngine = static_cast<const QFontEngineMulti *>(fontEngine)->engine(0);
Q_ASSERT(fontEngine);
}
Q_ASSERT(fontEngine->type() == QFontEngine::Mac);
QColor textColor = palette().text().color();
auto nsColor = [NSColor colorWithSRGBRed:textColor.redF()
green:textColor.greenF()
blue:textColor.blueF()
alpha:textColor.alphaF()];
if (font().styleStrategy() & QFont::NoAntialias)
CGContextSetShouldAntialias(ctx, false);
// Flip to what CT expects
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -height());
// Set up baseline
CGContextSetTextPosition(ctx, 0, height() - fontMetrics().ascent());
auto *attributedString = [[NSAttributedString alloc] initWithString:text().toNSString()
attributes:@{
NSFontAttributeName : (NSFont *)fontEngine->handle(),
NSForegroundColorAttributeName : nsColor
}
];
QCFType<CTLineRef> line = CTLineCreateWithAttributedString(CFAttributedStringRef([attributedString autorelease]));
CTLineDraw(line, ctx);
#endif
}
public:
RenderingMode m_mode = QtRendering;
QString m_text;
};
class TestWidget : public QWidget
{
Q_OBJECT
public:
TestWidget()
{
auto *mainLayout = new QVBoxLayout;
m_previews = new QWidget;
m_previews->setLayout(new QHBoxLayout);
for (int i = 0; i < 6; ++i) {
auto *layout = new QVBoxLayout;
QString text;
if (i > 0)
text = "ABC";
QPair<QColor, QColor> color = [i] {
switch (i) {
case 0: return qMakePair(QColor(), QColor());
case 1: return qMakePair(QColor(Qt::black), QColor(Qt::white));
case 2: return qMakePair(QColor(Qt::white), QColor(Qt::black));
case 3: return qMakePair(QColor(Qt::magenta), QColor(Qt::green));
case 4: return qMakePair(QColor(0, 0, 0, 128), QColor(Qt::white));
case 5: return qMakePair(QColor(255, 255, 255, 128), QColor(Qt::black));
default: return qMakePair(QColor(), QColor());
}
}();
for (int pointSize : {8, 12, 24, 36, 48})
layout->addWidget(new TextRenderer(pointSize, text, color.first, color.second));
static_cast<QHBoxLayout*>(m_previews->layout())->addLayout(layout);
}
mainLayout->addWidget(m_previews);
auto *controls = new QHBoxLayout;
auto *lineEdit = new QLineEdit(s_text);
connect(lineEdit, &QLineEdit::textChanged, [&](const QString &text) {
s_text = text;
for (TextRenderer *renderer : m_previews->findChildren<TextRenderer *>())
renderer->updateGeometry();
});
controls->addWidget(lineEdit);
auto *colorButton = new QPushButton("Color...");
connect(colorButton, &QPushButton::clicked, [&] {
auto *colorDialog = new QColorDialog(this);
colorDialog->setOptions(QColorDialog::NoButtons | QColorDialog::ShowAlphaChannel);
colorDialog->setModal(false);
connect(colorDialog, &QColorDialog::currentColorChanged, [&](const QColor &color) {
QPalette p = palette();
p.setColor(QPalette::Text, color);
setPalette(p);
});
colorDialog->setCurrentColor(palette().text().color());
colorDialog->setVisible(true);
});
controls->addWidget(colorButton);
auto *fontButton = new QPushButton("Font...");
connect(fontButton, &QPushButton::clicked, [&] {
auto *fontDialog = new QFontDialog(this);
fontDialog->setOptions(QFontDialog::NoButtons);
fontDialog->setModal(false);
fontDialog->setCurrentFont(m_previews->font());
connect(fontDialog, &QFontDialog::currentFontChanged, [&](const QFont &font) {
m_previews->setFont(font);
});
fontDialog->setVisible(true);
});
controls->addWidget(fontButton);
auto *aaButton = new QCheckBox("NoAntialias");
connect(aaButton, &QCheckBox::stateChanged, [&] {
for (TextRenderer *renderer : m_previews->findChildren<TextRenderer *>()) {
QFont font = renderer->font();
font.setStyleStrategy(QFont::StyleStrategy(font.styleStrategy() ^ QFont::NoAntialias));
renderer->setFont(font);
}
});
controls->addWidget(aaButton);
auto *subpixelAAButton = new QCheckBox("NoSubpixelAntialias");
connect(subpixelAAButton, &QCheckBox::stateChanged, [&] {
for (TextRenderer *renderer : m_previews->findChildren<TextRenderer *>()) {
QFont font = renderer->font();
font.setStyleStrategy(QFont::StyleStrategy(font.styleStrategy() ^ QFont::NoSubpixelAntialias));
renderer->setFont(font);
}
});
controls->addWidget(subpixelAAButton);
controls->addStretch();
mainLayout->addLayout(controls);
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(mainLayout);
setMode(TextRenderer::QtRendering);
setFocusPolicy(Qt::StrongFocus);
setFocus();
}
void setMode(TextRenderer::RenderingMode mode)
{
s_mode = mode;
setWindowTitle(s_mode == TextRenderer::QtRendering ? "Qt" : "Native");
for (TextRenderer *renderer : m_previews->findChildren<TextRenderer *>())
renderer->update();
}
void mousePressEvent(QMouseEvent *) override
{
setMode(TextRenderer::RenderingMode(!s_mode));
}
void keyPressEvent(QKeyEvent *e) override
{
if (e->key() == Qt::Key_Space)
setMode(TextRenderer::RenderingMode(!s_mode));
}
QWidget *m_previews;
};
int main(int argc, char **argv)
{
qputenv("QT_MAX_CACHED_GLYPH_SIZE", "97");
QApplication app(argc, argv);
TestWidget widget;
widget.show();
return app.exec();
}
#include "main.moc"

View File

@ -0,0 +1,7 @@
QT += widgets core-private gui-private
SOURCES += main.cpp
CONFIG -= app_bundle
darwin {
QMAKE_CXXFLAGS += -x objective-c++
LIBS += -framework Foundation -framework CoreGraphics -framework AppKit
}

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## textperformance Binary:
#####################################################################
qt_internal_add_manual_test(textperformance
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Gui
Qt::Widgets
)

View File

@ -0,0 +1,195 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QApplication>
#include <QDialog>
#include <QFontDatabase>
#include <QPainter>
#include <QRandomGenerator>
#include <QElapsedTimer>
#include <QTimer>
static const int lastMeasurementsCount = 50;
class FontBlaster: public QWidget
{
Q_OBJECT
public:
FontBlaster(QWidget *parent = nullptr)
: QWidget(parent)
, m_currentMode(0)
{
setFocusPolicy(Qt::StrongFocus);
}
void paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter p(this);
if (m_timer.isValid())
m_lastMeasurements.append(m_timer.elapsed());
m_timer.start();
p.save();
m_modes[m_currentMode].function(p, size());
p.restore();
const QFontMetrics fm = p.fontMetrics();
p.setOpacity(0.7);
p.fillRect(0, 0, width(), fm.height(), Qt::gray);
p.fillRect(0, height() - fm.height(), width(), height(), Qt::gray);
p.setOpacity(1);
p.setPen(palette().color(QPalette::Text));
p.drawText(2, fm.ascent(), m_modes[m_currentMode].name);
if (m_lastMeasurements.count() == lastMeasurementsCount) {
m_lastMeasurements.removeFirst();
int lastMsecsSum = 0;
foreach(const int measurement, m_lastMeasurements)
lastMsecsSum += measurement;
p.drawText(2, height() - fm.descent(),
QLatin1String("Fps: ") +
QString::number(1000 / ((qreal)lastMsecsSum / lastMeasurementsCount), 'f', 1)
);
}
QTimer::singleShot(0, this, SLOT(repaint()));
}
/*
Creating all kinds of size/weight/italic combinations, stress testing
the glyph cache.
Also: painting with different opacities, stress testing blitting.
*/
static void paintDifferentFontStyles(QPainter &p, const QSize &size)
{
static const QString text = QLatin1String("Qt rocks!!!");
static const int textsPerPaint = 30;
for (int i = 0; i < textsPerPaint; i++) {
const int fontSize = 4 + QRandomGenerator::global()->bounded(5);
const int fontWeight = QRandomGenerator::global()->bounded(2) == 1 ? QFont::Normal : QFont::Bold;
const bool fontItalic = QRandomGenerator::global()->bounded(2) == 1;
const QFont font("Default", fontSize, fontWeight, fontItalic);
p.setFont(font);
p.setPen(QColor::fromHsv(QRandomGenerator::global()->bounded(359), 155 + QRandomGenerator::global()->bounded(100),
155 + QRandomGenerator::global()->bounded(100), 100 + QRandomGenerator::global()->bounded(155)));
const QSize textSize(p.fontMetrics().boundingRect(text).size());
const QPoint position(
-textSize.width() / 2 + QRandomGenerator::global()->bounded(size.width()),
textSize.height() / 2 + QRandomGenerator::global()->bounded(size.height()));
p.drawText(position, text);
}
}
/*
Drawing a multiline latin text, stress testing the text layout system.
*/
static void paintLongLatinText(QPainter &p, const QSize &size)
{
static const char* const pieces[] = {
"lorem ipsum",
"dolor sit amet",
"consectetuer",
"sed diam nonumy",
"eos et accusam",
"sea takimata sanctus"
};
static const int piecesCount = (int)(sizeof pieces / sizeof pieces[0]);
static const int piecesPerPaint = 30;
QString text;
for (int i = 0; i < piecesPerPaint; ++i) {
QString piece = QLatin1String(pieces[QRandomGenerator::global()->bounded(piecesCount)]);
if (i == 0 || QRandomGenerator::global()->bounded(2)) {
// Make this piece the beginning of a new sentence.
piece[0] = piece[0].toUpper();
if (i > 0)
piece.prepend(QLatin1String(". "));
} else {
piece.prepend(QLatin1String(", "));
}
text.append(piece);
}
text.append(QLatin1Char('.'));
p.drawText(QRectF(QPointF(0, 0), QSizeF(size)),
Qt::AlignTop | Qt::AlignAbsolute | Qt::TextWordWrap, text);
}
/*
Drawing one text with several snippets of different writingSystems, stress
testing the font merging in the font database.
*/
static void paintInternationalText(QPainter &p, const QSize &size)
{
static QStringList samples;
if (samples.isEmpty()) {
foreach (const QFontDatabase::WritingSystem system, QFontDatabase::writingSystems())
if (system != QFontDatabase::Ogham && system != QFontDatabase::Runic)
samples.append(QFontDatabase::writingSystemSample(system));
}
static const int systemsPerPaint = 65;
QString text;
for (int i = 0; i < systemsPerPaint; i++) {
if (i > 0)
text.append(QLatin1Char(' '));
text.append(samples.at(QRandomGenerator::global()->bounded(samples.count())));
}
p.drawText(QRectF(QPointF(0, 0), QSizeF(size)),
Qt::AlignTop | Qt::AlignAbsolute | Qt::TextWordWrap, text);
}
protected:
void nextMode()
{
m_currentMode = (m_currentMode + 1) % m_modesCount;
m_lastMeasurements.clear();
}
void keyPressEvent(QKeyEvent *event)
{
Q_UNUSED(event);
nextMode();
}
void mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event);
nextMode();
}
private:
static const struct mode {
QString name;
void (*function)(QPainter &, const QSize&);
} m_modes[];
static const int m_modesCount;
int m_currentMode;
QList<int> m_lastMeasurements;
QElapsedTimer m_timer;
};
const struct FontBlaster::mode FontBlaster::m_modes[] = {
{ QLatin1String("Qt rocks!!!"), FontBlaster::paintDifferentFontStyles },
{ QLatin1String("Latin"), FontBlaster::paintLongLatinText },
{ QLatin1String("International"), FontBlaster::paintInternationalText }
};
const int FontBlaster::m_modesCount =
(int)(sizeof m_modes / sizeof m_modes[0]);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
FontBlaster dlg;
dlg.show();
return a.exec();
}
#include "main.moc"

View File

@ -0,0 +1,2 @@
QT += widgets
SOURCES = main.cpp

View File

@ -0,0 +1,5 @@
TEMPLATE=subdirs
SUBDIRS = glyphshaping \
textperformance \
nativetext