Use emoji segmenter to apply emoji fonts automatically

Colorful emojis in Unicode are not isolated to specific ranges
of code points like other writing systems. Instead, there are
a set of rules defining whether a sequence of characters should
be displayed in color or black/white.

    http://www.unicode.org/reports/tr51/

For instance, appending a variation selector to a character can
turn it into a color emoji, even if it is a code point that
predates the invention of emojis.

In addition, sequences of joined characters that are determined
to be a color emoji sequence should be parsed by a single emoji
font, so that it can apply things like skin color, etc.

In general, users expect emojis and emoji sequences to be shown
in the preferred color font of the system, even if a selected
font has black/white characters for the symbols.

This patch applies the emoji segmenter to strings to isolate
sequences that should be in color. As an implementation hack,
we mark this in the QScriptItems as a special "emoji" script.
Note that this is not a real Unicode script and only exists
internally for this reason, because the "emojiness" of the
resulting glyph overrides the original script of the
individual characters when selecting fonts. This way, we can
use a lot of the same logic for itemizing the strings and
looking up fonts, and we don't need to increase the size of
the QScriptItem. (It is just an implementation detail and
is not exposed to the user, so it can be replaced by other
approaches later if we need to.)

When matching an emoji sequence, we always try to apply a
color font and ignore all others. The exception is if there
is no color font at all on the system, then we will find a
black and white font which supports the characters instead
as a final failsafe.

In addition, each platform will put its default emoji font
at the top of the fallbacks list in order to make this the
preference in case there are more than one. This patch also
adds API to override this with an application-defined emoji
font, since this is a common use case.

Note: The font includes an environment variable to disable
the feature as a fail safe. A flag to disable it per QFont
will be added in a follow-up.

Fixes: QTBUG-111801
Change-Id: I9431ec34d56772ab8688814963073b83b23002ae
Reviewed-by: Lars Knoll <lars@knoll.priv.no>
Reviewed-by: <carl@carlschwan.eu>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2024-04-02 13:20:34 +02:00
parent 222cf3145e
commit 1685070930
36 changed files with 4627 additions and 175 deletions

View File

@ -327,6 +327,7 @@ struct FontDescription {
QFont::Stretch stretch;
qreal pointSize;
bool fixedPitch;
bool colorFont;
QSupportedWritingSystems writingSystems;
};
@ -359,6 +360,9 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
fd->style = QFont::StyleNormal;
fd->stretch = QFont::Unstretched;
fd->fixedPitch = false;
fd->colorFont = false;
if (QCFType<CTFontRef> tempFont = CTFontCreateWithFontDescriptor(font, 0.0, 0)) {
uint tag = QFont::Tag("OS/2").value();
@ -393,6 +397,9 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
if (CFNumberRef symbolic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSymbolicTrait)) {
int d;
if (CFNumberGetValue(symbolic, kCFNumberSInt32Type, &d)) {
if (d & kCTFontColorGlyphsTrait)
fd->colorFont = true;
if (d & kCTFontMonoSpaceTrait)
fd->fixedPitch = true;
if (d & kCTFontExpandedTrait)
@ -451,7 +458,7 @@ void QCoreTextFontDatabase::populateFromDescriptor(CTFontDescriptorRef font, con
CFRetain(font);
QPlatformFontDatabase::registerFont(family, fd.styleName, fd.foundryName, fd.weight, fd.style, fd.stretch,
true /* antialiased */, true /* scalable */, 0 /* pixelSize, ignored as font is scalable */,
fd.fixedPitch, fd.writingSystems, (void *)font);
fd.fixedPitch, fd.colorFont, fd.writingSystems, (void *)font);
}
static NSString * const kQtFontDataAttribute = @"QtFontDataAttribute";
@ -629,7 +636,18 @@ CTFontDescriptorRef descriptorForStyle(QFont::StyleHint styleHint)
}
}
QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
QStringList QCoreTextFontDatabase::fallbacksForScript(QFontDatabasePrivate::ExtendedScript script) const
{
if (script == QFontDatabasePrivate::Script_Emoji)
return QStringList{} << QStringLiteral(".Apple Color Emoji UI");
else
return QStringList{};
}
QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const
{
Q_UNUSED(style);
@ -639,7 +657,7 @@ QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFo
QMacAutoReleasePool pool;
QStringList fallbackList;
QStringList fallbackList = fallbacksForScript(script);
QCFType<CFArrayRef> fallbackFonts = fallbacksForFamily(family);
if (!fallbackFonts || !CFArrayGetCount(fallbackFonts)) {
@ -702,32 +720,34 @@ QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFo
fallbackList.append(QStringLiteral("Apple Symbols"));
// Some Noto* fonts are not automatically enumerated by system, despite being the main
// fonts for their writing system.
QString hardcodedFont = m_hardcodedFallbackFonts.value(script);
if (!hardcodedFont.isEmpty() && !fallbackList.contains(hardcodedFont)) {
if (!isFamilyPopulated(hardcodedFont)) {
if (!m_privateFamilies.contains(hardcodedFont)) {
QCFType<CTFontDescriptorRef> familyDescriptor = descriptorForFamily(hardcodedFont);
QCFType<CFArrayRef> matchingFonts = CTFontDescriptorCreateMatchingFontDescriptors(familyDescriptor, nullptr);
if (matchingFonts) {
const int numFonts = CFArrayGetCount(matchingFonts);
for (int i = 0; i < numFonts; ++i)
const_cast<QCoreTextFontDatabase *>(this)->populateFromDescriptor(CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingFonts, i)),
hardcodedFont);
if (script < int(QChar::ScriptCount)) {
QString hardcodedFont = m_hardcodedFallbackFonts.value(QChar::Script(script));
if (!hardcodedFont.isEmpty() && !fallbackList.contains(hardcodedFont)) {
if (!isFamilyPopulated(hardcodedFont)) {
if (!m_privateFamilies.contains(hardcodedFont)) {
QCFType<CTFontDescriptorRef> familyDescriptor = descriptorForFamily(hardcodedFont);
QCFType<CFArrayRef> matchingFonts = CTFontDescriptorCreateMatchingFontDescriptors(familyDescriptor, nullptr);
if (matchingFonts) {
const int numFonts = CFArrayGetCount(matchingFonts);
for (int i = 0; i < numFonts; ++i)
const_cast<QCoreTextFontDatabase *>(this)->populateFromDescriptor(CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingFonts, i)),
hardcodedFont);
fallbackList.append(hardcodedFont);
fallbackList.append(hardcodedFont);
}
// Register as private family even if the font is not found, in order to avoid
// redoing the check later. In later calls, the font will then just be ignored.
m_privateFamilies.insert(hardcodedFont);
}
// Register as private family even if the font is not found, in order to avoid
// redoing the check later. In later calls, the font will then just be ignored.
m_privateFamilies.insert(hardcodedFont);
} else {
fallbackList.append(hardcodedFont);
}
} else {
fallbackList.append(hardcodedFont);
}
}
#endif
extern QStringList qt_sort_families_by_writing_system(QChar::Script, const QStringList &);
extern QStringList qt_sort_families_by_writing_system(QFontDatabasePrivate::ExtendedScript, const QStringList &);
fallbackList = qt_sort_families_by_writing_system(script, fallbackList);
qCDebug(lcQpaFonts).nospace() << "Fallback families ordered by script " << script << ": " << fallbackList;

View File

@ -39,7 +39,7 @@ public:
void populateFamily(const QString &familyName) override;
void invalidate() override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QFontDatabasePrivate::ExtendedScript script) const override;
QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override;
void releaseHandle(void *handle) override;
bool isPrivateFontFamily(const QString &family) const override;
@ -55,6 +55,7 @@ private:
void populateThemeFonts();
void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString(), QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr);
static CFArrayRef fallbacksForFamily(const QString &family);
QStringList fallbacksForScript(QFontDatabasePrivate::ExtendedScript script) const;
QHash<QPlatformTheme::Font, QFont *> m_themeFonts;
QHash<QString, QList<QCFType<CTFontDescriptorRef>>> m_systemFontDescriptors;

View File

@ -101,6 +101,7 @@ void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_,
QFont::Stretch stretch,
QFont::Style style,
bool fixedPitch,
bool isColor,
const QSupportedWritingSystems &writingSystems,
const QByteArray &fileName,
const QByteArray &fontData)
@ -181,6 +182,7 @@ void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_,
true,
0,
fixedPitch,
isColor,
writingSystems,
variantFontFile);
}
@ -222,6 +224,12 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q
}
numFaces = face->num_faces;
#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20501
bool isColor = FT_HAS_COLOR(face);
#else
bool isColor = false;
#endif
QFont::Weight weight = QFont::Normal;
QFont::Style style = QFont::StyleNormal;
@ -335,9 +343,9 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q
applicationFont->properties.append(properties);
}
registerFont(family, styleName, QString(), weight, style, stretch, true, true, 0, fixedPitch, writingSystems, fontFile);
registerFont(family, styleName, QString(), weight, style, stretch, true, true, 0, fixedPitch, isColor, writingSystems, fontFile);
addNamedInstancesForFace(face, index, family, styleName, weight, stretch, style, fixedPitch, writingSystems, file, fontData);
addNamedInstancesForFace(face, index, family, styleName, weight, stretch, style, fixedPitch, isColor, writingSystems, file, fontData);
families.append(family);

View File

@ -47,7 +47,7 @@ public:
static void addNamedInstancesForFace(void *face, int faceIndex,
const QString &family, const QString &styleName,
QFont::Weight weight, QFont::Stretch stretch,
QFont::Style style, bool fixedPitch,
QFont::Style style, bool fixedPitch, bool isColor,
const QSupportedWritingSystems &writingSystems,
const QByteArray &fileName, const QByteArray &fontData);

View File

@ -383,13 +383,13 @@ void QFontPrivate::unsetFeature(QFont::Tag tag)
QFontEngineData::QFontEngineData()
: ref(0), fontCacheId(QFontCache::instance()->id())
{
memset(engines, 0, QChar::ScriptCount * sizeof(QFontEngine *));
memset(engines, 0, QFontDatabasePrivate::ScriptCount * sizeof(QFontEngine *));
}
QFontEngineData::~QFontEngineData()
{
Q_ASSERT(ref.loadRelaxed() == 0);
for (int i = 0; i < QChar::ScriptCount; ++i) {
for (int i = 0; i < QFontDatabasePrivate::ScriptCount; ++i) {
if (engines[i]) {
if (!engines[i]->ref.deref())
delete engines[i];
@ -2681,8 +2681,10 @@ void QFont::clearFeatures()
d->features.clear();
}
extern QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style,
QFont::StyleHint styleHint, QChar::Script script);
extern QStringList qt_fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script);
/*!
\fn QString QFont::defaultFamily() const
@ -2694,8 +2696,10 @@ extern QStringList qt_fallbacksForFamily(const QString &family, QFont::Style sty
*/
QString QFont::defaultFamily() const
{
const QStringList fallbacks = qt_fallbacksForFamily(QString(), QFont::StyleNormal
, QFont::StyleHint(d->request.styleHint), QChar::Script_Common);
const QStringList fallbacks = qt_fallbacksForFamily(QString(),
QFont::StyleNormal,
QFont::StyleHint(d->request.styleHint),
QFontDatabasePrivate::Script_Common);
if (!fallbacks.isEmpty())
return fallbacks.first();
return QString();
@ -3402,7 +3406,7 @@ void QFontCache::clear()
end = engineDataCache.end();
while (it != end) {
QFontEngineData *data = it.value();
for (int i = 0; i < QChar::ScriptCount; ++i) {
for (int i = 0; i < QFontDatabasePrivate::ScriptCount; ++i) {
if (data->engines[i]) {
if (!data->engines[i]->ref.deref()) {
Q_ASSERT(engineCacheCount.value(data->engines[i]) == 0);

View File

@ -24,6 +24,7 @@
#include "QtCore/qstringlist.h"
#include <QtGui/qfontdatabase.h>
#include "private/qfixed_p.h"
#include "private/qfontdatabase_p.h"
QT_BEGIN_NAMESPACE
@ -153,8 +154,7 @@ public:
QAtomicInt ref;
const int fontCacheId;
QFontEngine *engines[QChar::ScriptCount];
QFontEngine *engines[QFontDatabasePrivate::ScriptCount];
private:
Q_DISABLE_COPY_MOVE(QFontEngineData)
};

View File

@ -413,6 +413,8 @@ static bool familySupportsWritingSystem(QtFontFamily *family, size_t writingSyst
Q_GUI_EXPORT QFontDatabase::WritingSystem qt_writing_system_for_script(int script)
{
if (script >= QChar::ScriptCount)
return QFontDatabase::Any;
return QFontDatabase::WritingSystem(std::find(scriptForWritingSystem,
scriptForWritingSystem + QFontDatabase::WritingSystemsCount,
script) - scriptForWritingSystem);
@ -537,7 +539,7 @@ QFontDatabasePrivate *QFontDatabasePrivate::instance()
void qt_registerFont(const QString &familyName, const QString &stylename,
const QString &foundryname, int weight,
QFont::Style style, int stretch, bool antialiased,
bool scalable, int pixelSize, bool fixedPitch,
bool scalable, int pixelSize, bool fixedPitch, bool colorFont,
const QSupportedWritingSystems &writingSystems, void *handle)
{
auto *d = QFontDatabasePrivate::instance();
@ -549,6 +551,7 @@ void qt_registerFont(const QString &familyName, const QString &stylename,
styleKey.stretch = stretch;
QtFontFamily *f = d->family(familyName, QFontDatabasePrivate::EnsureCreated);
f->fixedPitch = fixedPitch;
f->colorFont = colorFont;
for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) {
if (writingSystems.supported(QFontDatabase::WritingSystem(i)))
@ -620,7 +623,10 @@ bool qt_isFontFamilyPopulated(const QString &familyName)
Default implementation returns a list of fonts for which \a style and \a script support
has been reported during the font database population.
*/
QStringList QPlatformFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
QStringList QPlatformFontDatabase::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const
{
Q_UNUSED(family);
Q_UNUSED(styleHint);
@ -659,7 +665,10 @@ QStringList QPlatformFontDatabase::fallbacksForFamily(const QString &family, QFo
return preferredFallbacks + otherFallbacks;
}
static QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script)
static QStringList fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script)
{
QMutexLocker locker(fontDatabaseMutex());
auto *db = QFontDatabasePrivate::ensureFontDatabase();
@ -670,7 +679,7 @@ static QStringList fallbacksForFamily(const QString &family, QFont::Style style,
return *fallbacks;
// make sure that the db has all fallback families
QStringList userFallbacks = db->applicationFallbackFontFamilies.value(script == QChar::Script_Latin ? QChar::Script_Common : script);
QStringList userFallbacks = db->applicationFallbackFontFamilies(script == QFontDatabasePrivate::Script_Latin ? QFontDatabasePrivate::Script_Common : script);
QStringList retList = userFallbacks + QGuiApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script);
QStringList::iterator i;
@ -693,7 +702,7 @@ static QStringList fallbacksForFamily(const QString &family, QFont::Style style,
return retList;
}
QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script)
QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QFontDatabasePrivate::ExtendedScript script)
{
QMutexLocker locker(fontDatabaseMutex());
return fallbacksForFamily(family, style, styleHint, script);
@ -722,10 +731,10 @@ QFontEngine *QFontDatabasePrivate::loadSingleEngine(int script,
QFontCache::Key key(def,script);
QFontEngine *engine = fontCache->findEngine(key);
if (!engine) {
const bool cacheForCommonScript = script != QChar::Script_Common
const bool cacheForCommonScript = script != QFontDatabasePrivate::Script_Common
&& (family->writingSystems[QFontDatabase::Latin] & QtFontFamily::Supported) != 0;
if (Q_LIKELY(cacheForCommonScript)) {
if (Q_LIKELY(cacheForCommonScript) && script < QChar::ScriptCount) {
// fast path: check if engine was loaded for another script
key.script = QChar::Script_Common;
engine = fontCache->findEngine(key);
@ -757,7 +766,7 @@ QFontEngine *QFontDatabasePrivate::loadSingleEngine(int script,
engine = pfdb->fontEngine(def, size->handle);
if (engine) {
// Also check for OpenType tables when using complex scripts
if (!engine->supportsScript(QChar::Script(script))) {
if (script < QChar::ScriptCount && !engine->supportsScript(QChar::Script(script))) {
qCInfo(lcFontDb, "OpenType support missing for \"%ls\", script %d",
qUtf16Printable(def.families.constFirst()), script);
if (engine->ref.loadRelaxed() == 0)
@ -789,7 +798,8 @@ QFontEngine *QFontDatabasePrivate::loadEngine(int script, const QFontDef &reques
Q_TRACE(QFontDatabase_loadEngine, request.families.join(QLatin1Char(';')), request.pointSize);
QPlatformFontDatabase *pfdb = QGuiApplicationPrivate::platformIntegration()->fontDatabase();
QFontEngineMulti *pfMultiEngine = pfdb->fontEngineMulti(engine, QChar::Script(script));
QFontEngineMulti *pfMultiEngine = pfdb->fontEngineMulti(engine,
QFontDatabasePrivate::ExtendedScript(script));
if (!request.fallBackFamilies.isEmpty()) {
QStringList fallbacks = request.fallBackFamilies;
@ -797,7 +807,10 @@ QFontEngine *QFontDatabasePrivate::loadEngine(int script, const QFontDef &reques
if (styleHint == QFont::AnyStyle && request.fixedPitch)
styleHint = QFont::TypeWriter;
fallbacks += fallbacksForFamily(family->name, QFont::Style(style->key.style), styleHint, QChar::Script(script));
fallbacks += fallbacksForFamily(family->name,
QFont::Style(style->key.style),
styleHint,
QFontDatabasePrivate::ExtendedScript(script));
pfMultiEngine->setFallbackFamiliesList(fallbacks);
}
@ -877,8 +890,10 @@ unsigned int QFontDatabasePrivate::bestFoundry(int script, unsigned int score, i
desc->style = nullptr;
desc->size = nullptr;
qCDebug(lcFontMatch, " REMARK: looking for best foundry for family '%s' [%d]", family->name.toLatin1().constData(), family->count);
qCDebug(lcFontMatch, " REMARK: looking for best foundry for family '%s'%s [%d]",
family->name.toLatin1().constData(),
family->colorFont ? " (color font)" : "",
family->count);
for (int x = 0; x < family->count; ++x) {
QtFontFoundry *foundry = family->foundries[x];
@ -1068,6 +1083,10 @@ int QFontDatabasePrivate::match(int script, const QFontDef &request, const QStri
if (writingSystem != QFontDatabase::Any && !familySupportsWritingSystem(test.family, writingSystem))
continue;
// Check if we require a color font and check for match
if (script == QFontDatabasePrivate::Script_Emoji && !test.family->colorFont)
continue;
// as we know the script is supported, we can be sure
// to find a matching font here.
unsigned int newscore =
@ -2202,6 +2221,48 @@ bool QFontDatabasePrivate::isApplicationFont(const QString &fileName)
return false;
}
void QFontDatabasePrivate::setApplicationFallbackFontFamilies(ExtendedScript script, const QStringList &familyNames)
{
applicationFallbackFontFamiliesHash[script] = familyNames;
QFontCache::instance()->clear();
fallbacksCache.clear();
}
QStringList QFontDatabasePrivate::applicationFallbackFontFamilies(ExtendedScript script)
{
return applicationFallbackFontFamiliesHash.value(script);
}
bool QFontDatabasePrivate::removeApplicationFallbackFontFamily(ExtendedScript script, const QString &familyName)
{
auto it = applicationFallbackFontFamiliesHash.find(script);
if (it != applicationFallbackFontFamiliesHash.end()) {
if (it->removeAll(familyName) > 0) {
if (it->isEmpty())
it = applicationFallbackFontFamiliesHash.erase(it);
QFontCache::instance()->clear();
fallbacksCache.clear();
return true;
}
}
return false;
}
void QFontDatabasePrivate::addApplicationFallbackFontFamily(ExtendedScript script, const QString &familyName)
{
auto it = applicationFallbackFontFamiliesHash.find(script);
if (it == applicationFallbackFontFamiliesHash.end())
it = applicationFallbackFontFamiliesHash.insert(script, QStringList{});
it->prepend(familyName);
QFontCache::instance()->clear();
fallbacksCache.clear();
}
/*!
\since 4.2
@ -2398,7 +2459,7 @@ void QFontDatabase::addApplicationFallbackFontFamily(QChar::Script script, const
{
QMutexLocker locker(fontDatabaseMutex());
if (script < QChar::Script_Common) {
if (script < QChar::Script_Common || script >= QChar::ScriptCount) {
qCWarning(lcFontDb) << "Invalid script passed to addApplicationFallbackFontFamily:" << script;
return;
}
@ -2407,14 +2468,7 @@ void QFontDatabase::addApplicationFallbackFontFamily(QChar::Script script, const
script = QChar::Script_Common;
auto *db = QFontDatabasePrivate::instance();
auto it = db->applicationFallbackFontFamilies.find(script);
if (it == db->applicationFallbackFontFamilies.end())
it = db->applicationFallbackFontFamilies.insert(script, QStringList{});
it->prepend(familyName);
QFontCache::instance()->clear();
db->fallbacksCache.clear();
db->addApplicationFallbackFontFamily(QFontDatabasePrivate::ExtendedScript(script), familyName);
}
/*!
@ -2431,7 +2485,7 @@ bool QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script script, co
{
QMutexLocker locker(fontDatabaseMutex());
if (script < QChar::Script_Common) {
if (script < QChar::Script_Common || script >= QChar::ScriptCount) {
qCWarning(lcFontDb) << "Invalid script passed to removeApplicationFallbackFontFamily:" << script;
return false;
}
@ -2440,18 +2494,8 @@ bool QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script script, co
script = QChar::Script_Common;
auto *db = QFontDatabasePrivate::instance();
auto it = db->applicationFallbackFontFamilies.find(script);
if (it != db->applicationFallbackFontFamilies.end()) {
if (it->removeAll(familyName) > 0) {
if (it->isEmpty())
it = db->applicationFallbackFontFamilies.erase(it);
QFontCache::instance()->clear();
db->fallbacksCache.clear();
return true;
}
}
return false;
return db->removeApplicationFallbackFontFamily(QFontDatabasePrivate::ExtendedScript(script),
familyName);
}
/*!
@ -2471,7 +2515,7 @@ void QFontDatabase::setApplicationFallbackFontFamilies(QChar::Script script, con
{
QMutexLocker locker(fontDatabaseMutex());
if (script < QChar::Script_Common) {
if (script < QChar::Script_Common || script >= QChar::ScriptCount) {
qCWarning(lcFontDb) << "Invalid script passed to setApplicationFallbackFontFamilies:" << script;
return;
}
@ -2480,10 +2524,8 @@ void QFontDatabase::setApplicationFallbackFontFamilies(QChar::Script script, con
script = QChar::Script_Common;
auto *db = QFontDatabasePrivate::instance();
db->applicationFallbackFontFamilies[script] = familyNames;
QFontCache::instance()->clear();
db->fallbacksCache.clear();
db->setApplicationFallbackFontFamilies(QFontDatabasePrivate::ExtendedScript(script),
familyNames);
}
/*!
@ -2498,11 +2540,81 @@ QStringList QFontDatabase::applicationFallbackFontFamilies(QChar::Script script)
{
QMutexLocker locker(fontDatabaseMutex());
if (script >= QChar::ScriptCount) {
qCWarning(lcFontDb) << "Invalid script passed to applicationFallbackFontFamilies:" << script;
return QStringList{};
}
if (script == QChar::Script_Latin)
script = QChar::Script_Common;
auto *db = QFontDatabasePrivate::instance();
return db->applicationFallbackFontFamilies.value(script);
return db->applicationFallbackFontFamilies(QFontDatabasePrivate::ExtendedScript(script));
}
/*!
\since 6.9
Adds \a familyName as an application-defined emoji font.
For displaying multi-color emojis or emoji sequences, Qt will by default prefer the system
default emoji font. Sometimes the application may want to override the default, either to
achieve a specific visual style or to show emojis that are not supported by the system.
\sa removeApplicationEmojiFontFamily, setApplicationEmojiFontFamilies(), applicationEmojiFontFamilies(), addApplicationFallbackFontFamily()
*/
void QFontDatabase::addApplicationEmojiFontFamily(const QString &familyName)
{
QMutexLocker locker(fontDatabaseMutex());
auto *db = QFontDatabasePrivate::instance();
db->addApplicationFallbackFontFamily(QFontDatabasePrivate::Script_Emoji, familyName);
}
/*!
\since 6.9
Removes \a familyName from the list of application-defined emoji fonts,
provided that it has previously been added with \l{addApplicationEmojiFontFamily()}.
Returns true if the family name was in the list and false if it was not.
\sa addApplicationEmojiFontFamily(), setApplicationEmojiFontFamilies(), applicationEmojiFontFamilies(), removeApplicationFallbackFontFamily()
*/
bool QFontDatabase::removeApplicationEmojiFontFamily(const QString &familyName)
{
QMutexLocker locker(fontDatabaseMutex());
auto *db = QFontDatabasePrivate::instance();
return db->removeApplicationFallbackFontFamily(QFontDatabasePrivate::Script_Emoji,
familyName);
}
/*!
\since 6.9
Sets the list of application-defined emoji fonts to \a familyNames.
\sa addApplicationEmojiFontFamily(), removeApplicationEmojiFontFamily(), applicationEmojiFontFamilies(), setApplicationFallbackFontFamilies()
*/
void QFontDatabase::setApplicationEmojiFontFamilies(const QStringList &familyNames)
{
QMutexLocker locker(fontDatabaseMutex());
auto *db = QFontDatabasePrivate::instance();
db->setApplicationFallbackFontFamilies(QFontDatabasePrivate::Script_Emoji,
familyNames);
}
/*!
\since 6.9
Returns the list of application-defined emoji font families.
\sa addApplicationEmojiFontFamily(), removeApplicationEmojiFontFamily(), setApplicationEmojiFontFamilies(), applicationFallbackFontFamilies()
*/
QStringList QFontDatabase::applicationEmojiFontFamilies()
{
QMutexLocker locker(fontDatabaseMutex());
auto *db = QFontDatabasePrivate::instance();
return db->applicationFallbackFontFamilies(QFontDatabasePrivate::Script_Emoji);
}
/*!
@ -2559,12 +2671,27 @@ QFontEngine *QFontDatabasePrivate::findFont(const QFontDef &req,
QtFontDesc desc;
QList<int> blackListed;
unsigned int score = UINT_MAX;
int index = match(multi ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed, &score);
// 1.
// We start by looking up the family name and finding the best style/foundry. For multi fonts
// we always want the requested font to be on top, even if it does not support the selected
// script, since the fallback mechanism will handle this later. For NoFontMerging fonts, we pass
// in the script in order to prefer foundries that support the script. If none is found, we will
// retry with Script_Common later. Note that Script_Emoji is special. This means the Unicode
// algorithm has determined that we should use a color font. If the selected font is not
// a color font, we use the fall back mechanism to find one, since we want to prefer *any* color
// font over a non-color font in this case.
int index = match(multi && script != QFontDatabasePrivate::Script_Emoji ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed, &score);
// 2.
// If no font was found or it was not a perfect match, we let the database populate family
// aliases and try again.
if (score > 0 && QGuiApplicationPrivate::platformIntegration()->fontDatabase()->populateFamilyAliases(family_name)) {
// We populated family aliases (e.g. localized families), so try again
index = match(multi ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed);
index = match(multi && script != QFontDatabasePrivate::Script_Emoji ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed);
}
// 3.
// If we do not find a match and NoFontMerging is set, use the requested font even if it does
// not support the script.
//
@ -2589,6 +2716,10 @@ QFontEngine *QFontDatabasePrivate::findFont(const QFontDef &req,
qCDebug(lcFontMatch, " NO MATCH FOUND\n");
}
// 4.
// If no font matching the script + family exists, we go via the fallback mechanism. This
// happens when the family does not exist or if we want a color font and the requested font
// is not.
if (!engine) {
if (!requestFamily.isEmpty()) {
QFont::StyleHint styleHint = QFont::StyleHint(request.styleHint);
@ -2599,32 +2730,49 @@ QFontEngine *QFontDatabasePrivate::findFont(const QFontDef &req,
+ fallbacksForFamily(requestFamily,
QFont::Style(request.style),
styleHint,
QChar::Script(script));
QFontDatabasePrivate::ExtendedScript(script));
if (script > QChar::Script_Common)
fallbacks += QString(); // Find the first font matching the specified script.
for (int i = 0; !engine && i < fallbacks.size(); i++) {
QFontDef def = request;
def.families = QStringList(fallbacks.at(i));
QFontCache::Key key(def, script, multi ? 1 : 0);
engine = fontCache->findEngine(key);
if (!engine) {
QtFontDesc desc;
do {
index = match(multi ? QChar::Script_Common : script, def, def.families.constFirst(), ""_L1, &desc, blackListed);
if (index >= 0) {
QFontDef loadDef = def;
if (loadDef.families.isEmpty())
loadDef.families = QStringList(desc.family->name);
engine = loadEngine(script, loadDef, desc.family, desc.foundry, desc.style, desc.size);
if (engine)
initFontDef(desc, loadDef, &engine->fontDef, multi);
else
blackListed.append(index);
}
} while (index >= 0 && !engine);
auto findMatchingFallback = [&](int xscript) {
for (int i = 0; !engine && i < fallbacks.size(); i++) {
QFontDef def = request;
def.families = QStringList(fallbacks.at(i));
QFontCache::Key key(def, xscript, multi ? 1 : 0);
engine = fontCache->findEngine(key);
if (!engine) {
QtFontDesc desc;
do {
index = match(xscript,
def,
def.families.constFirst(),
""_L1,
&desc,
blackListed);
if (index >= 0) {
QFontDef loadDef = def;
if (loadDef.families.isEmpty())
loadDef.families = QStringList(desc.family->name);
engine = loadEngine(xscript, loadDef, desc.family, desc.foundry, desc.style, desc.size);
if (engine)
initFontDef(desc, loadDef, &engine->fontDef, multi);
else
blackListed.append(index);
}
} while (index >= 0 && !engine);
}
}
}
};
findMatchingFallback(multi && script != QFontDatabasePrivate::Script_Emoji ? QChar::Script_Common: script);
// If we are looking for a color font and there are no color fonts on the system,
// we will end up here, for one final pass. This is a rare occurrence so we accept
// and extra pass on the fallbacks for this.
if (!engine && script == QFontDatabasePrivate::Script_Emoji)
findMatchingFallback(QChar::Script_Common);
}
if (!engine) {
@ -2721,7 +2869,7 @@ void QFontDatabasePrivate::load(const QFontPrivate *d, int script)
Q_ASSERT(fe);
if (fe->symbol || (d->request.styleStrategy & QFont::NoFontMerging)) {
for (int i = 0; i < QChar::ScriptCount; ++i) {
for (int i = 0; i < QFontDatabasePrivate::ScriptCount; ++i) {
if (!d->engineData->engines[i]) {
d->engineData->engines[i] = fe;
fe->ref.ref();
@ -2738,11 +2886,13 @@ QString QFontDatabasePrivate::resolveFontFamilyAlias(const QString &family)
return QGuiApplicationPrivate::platformIntegration()->fontDatabase()->resolveFontFamilyAlias(family);
}
Q_GUI_EXPORT QStringList qt_sort_families_by_writing_system(QChar::Script script, const QStringList &families)
Q_GUI_EXPORT QStringList qt_sort_families_by_writing_system(QFontDatabasePrivate::ExtendedScript script,
const QStringList &families)
{
size_t writingSystem = qt_writing_system_for_script(script);
if (writingSystem == QFontDatabase::Any
|| writingSystem >= QFontDatabase::WritingSystemsCount) {
if (script != QFontDatabasePrivate::Script_Emoji
&& (writingSystem == QFontDatabase::Any
|| writingSystem >= QFontDatabase::WritingSystemsCount)) {
return families;
}
@ -2762,7 +2912,8 @@ Q_GUI_EXPORT QStringList qt_sort_families_by_writing_system(QChar::Script script
uint order = i;
if (testFamily == nullptr
|| !familySupportsWritingSystem(testFamily, writingSystem)) {
|| (script == QFontDatabasePrivate::Script_Emoji && !testFamily->colorFont)
|| (script != QFontDatabasePrivate::Script_Emoji && !familySupportsWritingSystem(testFamily, writingSystem))) {
order |= 1u << 31;
}

View File

@ -117,6 +117,11 @@ public:
static void setApplicationFallbackFontFamilies(QChar::Script, const QStringList &familyNames);
static QStringList applicationFallbackFontFamilies(QChar::Script script);
static void addApplicationEmojiFontFamily(const QString &familyName);
static bool removeApplicationEmojiFontFamily(const QString &familyName);
static void setApplicationEmojiFontFamilies(const QStringList &familyNames);
static QStringList applicationEmojiFontFamilies();
static QFont systemFont(SystemFont type);
};

View File

@ -33,7 +33,7 @@ struct QtFontFallbacksCacheKey
QString family;
QFont::Style style;
QFont::StyleHint styleHint;
QChar::Script script;
int script;
};
inline bool operator==(const QtFontFallbacksCacheKey &lhs, const QtFontFallbacksCacheKey &rhs) noexcept
@ -155,6 +155,7 @@ struct Q_GUI_EXPORT QtFontFamily
:
populated(false),
fixedPitch(false),
colorFont(false),
name(n), count(0), foundries(nullptr)
{
memset(writingSystems, 0, sizeof(writingSystems));
@ -167,6 +168,7 @@ struct Q_GUI_EXPORT QtFontFamily
bool populated : 1;
bool fixedPitch : 1;
bool colorFont : 1;
QString name;
QStringList aliases;
@ -202,13 +204,21 @@ public:
EnsurePopulated
};
// Expands QChar::Script by adding a special "script" for emoji sequences
enum ExtendedScript {
Script_Common = QChar::Script_Common,
Script_Latin = QChar::Script_Latin,
Script_Emoji = QChar::ScriptCount,
ScriptCount
};
QtFontFamily *family(const QString &f, FamilyRequestFlags flags = EnsurePopulated);
int count;
QtFontFamily **families;
bool populated = false;
QHash<QChar::Script, QStringList> applicationFallbackFontFamilies;
QHash<ExtendedScript, QStringList> applicationFallbackFontFamiliesHash;
QCache<QtFontFallbacksCacheKey, QStringList> fallbacksCache;
struct ApplicationFont {
@ -236,21 +246,30 @@ public:
int addAppFont(const QByteArray &fontData, const QString &fileName);
bool isApplicationFont(const QString &fileName);
void setApplicationFallbackFontFamilies(ExtendedScript script, const QStringList &familyNames);
QStringList applicationFallbackFontFamilies(ExtendedScript script);
bool removeApplicationFallbackFontFamily(ExtendedScript script, const QString &familyName);
void addApplicationFallbackFontFamily(ExtendedScript script, const QString &familyName);
static QFontDatabasePrivate *instance();
static void parseFontName(const QString &name, QString &foundry, QString &family);
static QString resolveFontFamilyAlias(const QString &family);
static QFontEngine *findFont(const QFontDef &request,
int script /* QChar::Script */,
int script /* QFontDatabasePrivate::ExtendedScript */,
bool preferScriptOverFamily = false);
static void load(const QFontPrivate *d, int script /* QChar::Script */);
static void load(const QFontPrivate *d, int script /* QFontDatabasePrivate::ExtendedScript */);
static QFontDatabasePrivate *ensureFontDatabase();
void invalidate();
private:
static int match(int script, const QFontDef &request, const QString &family_name,
const QString &foundry_name, QtFontDesc *desc, const QList<int> &blacklistedFamilies,
static int match(int script,
const QFontDef &request,
const QString &family_name,
const QString &foundry_name,
QtFontDesc *desc,
const QList<int> &blacklistedFamilies,
unsigned int *resultingScore = nullptr);
static unsigned int bestFoundry(int script, unsigned int score, int styleStrategy,

View File

@ -1747,7 +1747,7 @@ QFontEngineMulti::~QFontEngineMulti()
}
}
QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script);
QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QFontDatabasePrivate::ExtendedScript script);
void QFontEngineMulti::ensureFallbackFamiliesQueried()
{
@ -1757,7 +1757,7 @@ void QFontEngineMulti::ensureFallbackFamiliesQueried()
setFallbackFamiliesList(qt_fallbacksForFamily(fontDef.families.constFirst(),
QFont::Style(fontDef.style), styleHint,
QChar::Script(m_script)));
QFontDatabasePrivate::ExtendedScript(m_script)));
}
void QFontEngineMulti::setFallbackFamiliesList(const QStringList &fallbackFamilies)
@ -1805,7 +1805,7 @@ QFontEngine *QFontEngineMulti::loadEngine(int at)
// info about the actual script of the characters may have been discarded,
// so we do not check for writing system support, but instead just load
// the family indiscriminately.
if (QFontEngine *engine = QFontDatabasePrivate::findFont(request, QChar::Script_Common)) {
if (QFontEngine *engine = QFontDatabasePrivate::findFont(request, QFontDatabasePrivate::Script_Common)) {
engine->fontDef.weight = request.weight;
if (request.style > QFont::StyleNormal)
engine->fontDef.style = request.style;
@ -2381,7 +2381,7 @@ QFontEngine *QFontEngineMulti::createMultiFontEngine(QFontEngine *fe, int script
++it;
}
if (!engine) {
engine = QGuiApplicationPrivate::instance()->platformIntegration()->fontDatabase()->fontEngineMulti(fe, QChar::Script(script));
engine = QGuiApplicationPrivate::instance()->platformIntegration()->fontDatabase()->fontEngineMulti(fe, QFontDatabasePrivate::ExtendedScript(script));
fc->insertEngine(key, engine, /* insertMulti */ !faceIsLocal);
}
Q_ASSERT(engine);

View File

@ -24,7 +24,7 @@ Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
void qt_registerFont(const QString &familyname, const QString &stylename,
const QString &foundryname, int weight,
QFont::Style style, int stretch, bool antialiased,
bool scalable, int pixelSize, bool fixedPitch,
bool scalable, int pixelSize, bool fixedPitch, bool colorFont,
const QSupportedWritingSystems &writingSystems, void *hanlde);
void qt_registerFontFamily(const QString &familyName);
@ -55,7 +55,7 @@ bool qt_isFontFamilyPopulated(const QString &familyName);
void QPlatformFontDatabase::registerFont(const QString &familyname, const QString &stylename,
const QString &foundryname, QFont::Weight weight,
QFont::Style style, QFont::Stretch stretch, bool antialiased,
bool scalable, int pixelSize, bool fixedPitch,
bool scalable, int pixelSize, bool fixedPitch, bool colorFont,
const QSupportedWritingSystems &writingSystems, void *usrPtr)
{
if (scalable)
@ -63,7 +63,7 @@ void QPlatformFontDatabase::registerFont(const QString &familyname, const QStrin
qt_registerFont(familyname, stylename, foundryname, weight, style,
stretch, antialiased, scalable, pixelSize,
fixedPitch, writingSystems, usrPtr);
fixedPitch, colorFont, writingSystems, usrPtr);
}
/*!
@ -271,7 +271,8 @@ void QPlatformFontDatabase::invalidate()
option to fall back to the fonts given by \a fallbacks if \a fontEngine does not support
a certain character.
*/
QFontEngineMulti *QPlatformFontDatabase::fontEngineMulti(QFontEngine *fontEngine, QChar::Script script)
QFontEngineMulti *QPlatformFontDatabase::fontEngineMulti(QFontEngine *fontEngine,
QFontDatabasePrivate::ExtendedScript script)
{
return new QFontEngineMulti(fontEngine, script);
}

View File

@ -20,7 +20,6 @@
#include <QtCore/QList>
#include <QtGui/QFontDatabase>
#include <QtGui/private/qfontengine_p.h>
#include <QtGui/private/qfont_p.h>
#include <QtGui/private/qfontdatabase_p.h>
QT_BEGIN_NAMESPACE
@ -72,12 +71,12 @@ public:
virtual void populateFamily(const QString &familyName);
virtual void invalidate();
virtual QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const;
virtual QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QFontDatabasePrivate::ExtendedScript script) const;
virtual QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *font = nullptr);
virtual QFontEngine *fontEngine(const QFontDef &fontDef, void *handle);
virtual QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference);
virtual QFontEngineMulti *fontEngineMulti(QFontEngine *fontEngine, QChar::Script script);
virtual QFontEngineMulti *fontEngineMulti(QFontEngine *fontEngine, QFontDatabasePrivate::ExtendedScript script);
virtual void releaseHandle(void *handle);
virtual QString fontDir() const;
@ -99,7 +98,7 @@ public:
static void registerFont(const QString &familyname, const QString &stylename,
const QString &foundryname, QFont::Weight weight,
QFont::Style style, QFont::Stretch stretch, bool antialiased,
bool scalable, int pixelSize, bool fixedPitch,
bool scalable, int pixelSize, bool fixedPitch, bool colorFont,
const QSupportedWritingSystems &writingSystems, void *handle);
static void registerFontFamily(const QString &familyName);

View File

@ -1409,8 +1409,8 @@ void QTextEngine::shapeText(int item) const
QFont font = f.font();
# if QT_CONFIG(harfbuzz)
kerningEnabled = font.kerning();
shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script))
|| (font.styleStrategy() & QFont::PreferNoShaping) == 0;
shapingEnabled = (si.analysis.script < QChar::ScriptCount && QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script)))
|| (font.styleStrategy() & QFont::PreferNoShaping) == 0;
# endif
wordSpacing = QFixed::fromReal(font.wordSpacing());
letterSpacing = QFixed::fromReal(font.letterSpacing());
@ -1422,8 +1422,8 @@ void QTextEngine::shapeText(int item) const
QFont font = this->font(si);
#if QT_CONFIG(harfbuzz)
kerningEnabled = font.d->kerning;
shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script))
|| (font.d->request.styleStrategy & QFont::PreferNoShaping) == 0;
shapingEnabled = (si.analysis.script < QChar::ScriptCount && QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script)))
|| (font.d->request.styleStrategy & QFont::PreferNoShaping) == 0;
#endif
letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute;
letterSpacing = font.d->letterSpacing;
@ -1619,7 +1619,9 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si,
hb_segment_properties_t props = HB_SEGMENT_PROPERTIES_DEFAULT;
props.direction = si.analysis.bidiLevel % 2 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
QChar::Script script = QChar::Script(si.analysis.script);
QChar::Script script = si.analysis.script < QChar::ScriptCount
? QChar::Script(si.analysis.script)
: QChar::Script_Common;
props.script = hb_qt_script_to_script(script);
// ### TODO get_default_for_script?
props.language = hb_language_get_default(); // use default language from locale
@ -1923,8 +1925,38 @@ void QTextEngine::validate() const
layoutData->string.insert(specialData->preeditPosition, specialData->preeditText);
}
#if !defined(QT_NO_EMOJISEGMENTER)
namespace {
enum CharacterCategory {
EMOJI = 0,
EMOJI_TEXT_PRESENTATION = 1,
EMOJI_EMOJI_PRESENTATION = 2,
EMOJI_MODIFIER_BASE = 3,
EMOJI_MODIFIER = 4,
EMOJI_VS_BASE = 5,
REGIONAL_INDICATOR = 6,
KEYCAP_BASE = 7,
COMBINING_ENCLOSING_KEYCAP = 8,
COMBINING_ENCLOSING_CIRCLE_BACKSLASH = 9,
ZWJ = 10,
VS15 = 11,
VS16 = 12,
TAG_BASE = 13,
TAG_SEQUENCE = 14,
TAG_TERM = 15,
OTHER = 16
};
typedef CharacterCategory *emoji_text_iter_t;
#include "../../3rdparty/emoji-segmenter/emoji_presentation_scanner.c"
}
#endif
void QTextEngine::itemize() const
{
static bool disableEmojiSegmenter = qEnvironmentVariableIntValue("QT_DISABLE_EMOJI_SEGMENTER") > 0;
validate();
if (layoutData->items.size())
return;
@ -1954,9 +1986,76 @@ void QTextEngine::itemize() const
}
}
#if !defined(QT_NO_EMOJISEGMENTER)
QVarLengthArray<CharacterCategory> categorizedString;
if (!disableEmojiSegmenter) {
// Parse emoji sequences
for (int i = 0; i < length; ++i) {
const QChar &c = string[i];
const bool isSurrogate = c.isHighSurrogate() && i < length - 1;
const char32_t ucs4 = isSurrogate
? QChar::surrogateToUcs4(c, string[++i])
: c.unicode();
const QUnicodeTables::Properties *p = QUnicodeTables::properties(ucs4);
if (ucs4 == 0x20E3)
categorizedString.append(CharacterCategory::COMBINING_ENCLOSING_KEYCAP);
else if (ucs4 == 0x20E0)
categorizedString.append(CharacterCategory::COMBINING_ENCLOSING_CIRCLE_BACKSLASH);
else if (ucs4 == 0xFE0E)
categorizedString.append(CharacterCategory::VS15);
else if (ucs4 == 0xFE0F)
categorizedString.append(CharacterCategory::VS16);
else if (ucs4 == 0x200D)
categorizedString.append(CharacterCategory::ZWJ);
else if (ucs4 == 0x1F3F4)
categorizedString.append(CharacterCategory::TAG_BASE);
else if (ucs4 == 0xE007F)
categorizedString.append(CharacterCategory::TAG_TERM);
else if ((ucs4 >= 0xE0030 && ucs4 <= 0xE0039) || (ucs4 >= 0xE0061 && ucs4 <= 0xE007A))
categorizedString.append(CharacterCategory::TAG_SEQUENCE);
else if (ucs4 >= 0x1F1E6 && ucs4 <= 0x1F1FF)
categorizedString.append(CharacterCategory::REGIONAL_INDICATOR);
// emoji_keycap_sequence = [0-9#*] \x{FE0F 20E3}
else if ((ucs4 >= 0x0030 && ucs4 <= 0x0039) || ucs4 == 0x0023 || ucs4 == 0x002A)
categorizedString.append(CharacterCategory::KEYCAP_BASE);
else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji_Modifier_Base))
categorizedString.append(CharacterCategory::EMOJI_MODIFIER_BASE);
else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji_Modifier))
categorizedString.append(CharacterCategory::EMOJI_MODIFIER);
else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji_Presentation))
categorizedString.append(CharacterCategory::EMOJI_EMOJI_PRESENTATION);
// If it's in the emoji list and doesn't have the emoji presentation, it is text
// presentation.
else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji))
categorizedString.append(CharacterCategory::EMOJI_TEXT_PRESENTATION);
else
categorizedString.append(CharacterCategory::OTHER);
}
}
#endif
const ushort *uc = string;
const ushort *e = uc + length;
#if !defined(QT_NO_EMOJISEGMENTER)
const emoji_text_iter_t categoriesStart = categorizedString.data();
const emoji_text_iter_t categoriesEnd = categoriesStart + categorizedString.size();
emoji_text_iter_t categoryIt = categoriesStart;
bool isEmoji = false;
bool hasVs = false;
emoji_text_iter_t nextIt = categoryIt;
#endif
while (uc < e) {
#if !defined(QT_NO_EMOJISEGMENTER)
// Find next emoji sequence
if (!disableEmojiSegmenter && categoryIt == nextIt)
nextIt = scan_emoji_presentation(categoryIt, categoriesEnd, &isEmoji, &hasVs);
#endif
switch (*uc) {
case QChar::ObjectReplacementCharacter:
{
@ -1996,7 +2095,27 @@ void QTextEngine::itemize() const
default:
analysis->flags = QScriptAnalysis::None;
break;
};
#if !defined(QT_NO_EMOJISEGMENTER)
if (!disableEmojiSegmenter) {
if (isEmoji) {
static_assert(QChar::ScriptCount < USHRT_MAX);
analysis->script = QFontDatabasePrivate::Script_Emoji;
}
if (QChar::isHighSurrogate(*uc) && (uc + 1) < e && QChar::isLowSurrogate(*(uc + 1))) {
if (isEmoji)
(analysis + 1)->script = QFontDatabasePrivate::Script_Emoji;
++uc;
++analysis;
}
++categoryIt;
}
#endif
++uc;
++analysis;
}

View File

@ -475,6 +475,12 @@ static void populateFromPattern(FcPattern *pattern,
FcPatternGetDouble (pattern, FC_PIXEL_SIZE, 0, &pixel_size);
bool fixedPitch = spacing_value >= FC_MONO;
FcBool colorFont = false;
#ifdef FC_COLOR
FcPatternGetBool(pattern, FC_COLOR, 1, &colorFont);
#endif
// Note: stretch should really be an int but registerFont incorrectly uses an enum
QFont::Stretch stretch = QFont::Stretch(stretchFromFcWidth(width_value));
QString styleName = style_value ? QString::fromUtf8((const char *) style_value) : QString();
@ -490,7 +496,7 @@ static void populateFromPattern(FcPattern *pattern,
applicationFont->properties.append(properties);
}
QPlatformFontDatabase::registerFont(familyName,styleName,QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,fixedPitch,writingSystems,fontFile);
QPlatformFontDatabase::registerFont(familyName,styleName,QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,fixedPitch,colorFont,writingSystems,fontFile);
if (applicationFont != nullptr && face != nullptr && db != nullptr) {
db->addNamedInstancesForFace(face,
indexValue,
@ -500,6 +506,7 @@ static void populateFromPattern(FcPattern *pattern,
stretch,
style,
fixedPitch,
colorFont,
writingSystems,
QByteArray((const char*)file_value),
applicationFont->data);
@ -536,7 +543,7 @@ static void populateFromPattern(FcPattern *pattern,
applicationFont->properties.append(properties);
}
FontFile *altFontFile = new FontFile(*fontFile);
QPlatformFontDatabase::registerFont(altFamilyName, altStyleName, QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,fixedPitch,writingSystems,altFontFile);
QPlatformFontDatabase::registerFont(altFamilyName, altStyleName, QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,fixedPitch,colorFont,writingSystems,altFontFile);
} else {
QPlatformFontDatabase::registerAliasToFontFamily(familyName, altFamilyName);
}
@ -613,9 +620,9 @@ void QFontconfigDatabase::populateFontDatabase()
while (f->qtname) {
QString familyQtName = QString::fromLatin1(f->qtname);
registerFont(familyQtName,QString(),QString(),QFont::Normal,QFont::StyleNormal,QFont::Unstretched,true,true,0,f->fixed,ws,nullptr);
registerFont(familyQtName,QString(),QString(),QFont::Normal,QFont::StyleItalic,QFont::Unstretched,true,true,0,f->fixed,ws,nullptr);
registerFont(familyQtName,QString(),QString(),QFont::Normal,QFont::StyleOblique,QFont::Unstretched,true,true,0,f->fixed,ws,nullptr);
registerFont(familyQtName,QString(),QString(),QFont::Normal,QFont::StyleNormal,QFont::Unstretched,true,true,false,0,f->fixed,ws,nullptr);
registerFont(familyQtName,QString(),QString(),QFont::Normal,QFont::StyleItalic,QFont::Unstretched,true,true,false,0,f->fixed,ws,nullptr);
registerFont(familyQtName,QString(),QString(),QFont::Normal,QFont::StyleOblique,QFont::Unstretched,true,true,false,0,f->fixed,ws,nullptr);
++f;
}
@ -634,7 +641,7 @@ void QFontconfigDatabase::invalidate()
FcConfigAppFontClear(nullptr);
}
QFontEngineMulti *QFontconfigDatabase::fontEngineMulti(QFontEngine *fontEngine, QChar::Script script)
QFontEngineMulti *QFontconfigDatabase::fontEngineMulti(QFontEngine *fontEngine, QFontDatabasePrivate::ExtendedScript script)
{
return new QFontEngineMultiFontConfig(fontEngine, script);
}
@ -758,7 +765,10 @@ QFontEngine *QFontconfigDatabase::fontEngine(const QByteArray &fontData, qreal p
return engine;
}
QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const
{
QStringList fallbackFamilies;
FcPattern *pattern = FcPatternCreate();
@ -771,6 +781,14 @@ QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont
value.u.s = (const FcChar8 *)cs.data();
FcPatternAdd(pattern,FC_FAMILY,value,true);
#ifdef FC_COLOR
if (script == QFontDatabasePrivate::Script_Emoji) {
FcPatternAddBool(pattern, FC_COLOR, true);
value.u.s = (const FcChar8 *)"emoji";
FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue);
}
#endif
int slant_value = FC_SLANT_ROMAN;
if (style == QFont::StyleItalic)
slant_value = FC_SLANT_ITALIC;
@ -778,8 +796,8 @@ QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont
slant_value = FC_SLANT_OBLIQUE;
FcPatternAddInteger(pattern, FC_SLANT, slant_value);
Q_ASSERT(uint(script) < QChar::ScriptCount);
if (*specialLanguages[script] != '\0') {
Q_ASSERT(uint(script) < QFontDatabasePrivate::ScriptCount);
if (uint(script) < QChar::ScriptCount && *specialLanguages[script] != '\0') {
FcLangSet *ls = FcLangSetCreate();
FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]);
FcPatternAddLangSet(pattern, FC_LANG, ls);

View File

@ -29,10 +29,14 @@ public:
void populateFontDatabase() override;
void invalidate() override;
bool supportsVariableApplicationFonts() const override;
QFontEngineMulti *fontEngineMulti(QFontEngine *fontEngine, QChar::Script script) override;
QFontEngineMulti *fontEngineMulti(QFontEngine *fontEngine,
QFontDatabasePrivate::ExtendedScript script) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const override;
QStringList fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const override;
QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override;
QString resolveFontFamilyAlias(const QString &family) const override;
QFont defaultFont() const override;

View File

@ -138,9 +138,9 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
for (uint j = 0; j < matchingFonts->GetFontCount(); ++j) {
DirectWriteScope<IDWriteFont> font;
if (SUCCEEDED(matchingFonts->GetFont(j, &font))) {
DirectWriteScope<IDWriteFont1> font1;
if (!SUCCEEDED(font->QueryInterface(__uuidof(IDWriteFont1),
reinterpret_cast<void **>(&font1)))) {
DirectWriteScope<IDWriteFont2> font2;
if (!SUCCEEDED(font->QueryInterface(__uuidof(IDWriteFont2),
reinterpret_cast<void **>(&font2)))) {
qCWarning(lcQpaFonts) << "COM object does not support IDWriteFont1";
continue;
}
@ -149,7 +149,7 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
QString englishLocaleFamilyName;
DirectWriteScope<IDWriteFontFamily> fontFamily2;
if (SUCCEEDED(font1->GetFontFamily(&fontFamily2))) {
if (SUCCEEDED(font2->GetFontFamily(&fontFamily2))) {
DirectWriteScope<IDWriteLocalizedStrings> names;
if (SUCCEEDED(fontFamily2->GetFamilyNames(&names))) {
defaultLocaleFamilyName = hasDefaultLocale ? localeString(*names, defaultLocale) : QString();
@ -162,14 +162,15 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
{
DirectWriteScope<IDWriteLocalizedStrings> names;
if (SUCCEEDED(font1->GetFaceNames(&names))) {
if (SUCCEEDED(font2->GetFaceNames(&names))) {
QString defaultLocaleStyleName = hasDefaultLocale ? localeString(*names, defaultLocale) : QString();
QString englishLocaleStyleName = localeString(*names, englishLocale);
QFont::Stretch stretch = fromDirectWriteStretch(font1->GetStretch());
QFont::Style style = fromDirectWriteStyle(font1->GetStyle());
QFont::Weight weight = fromDirectWriteWeight(font1->GetWeight());
bool fixed = font1->IsMonospacedFont();
QFont::Stretch stretch = fromDirectWriteStretch(font2->GetStretch());
QFont::Style style = fromDirectWriteStyle(font2->GetStyle());
QFont::Weight weight = fromDirectWriteWeight(font2->GetWeight());
bool fixed = font2->IsMonospacedFont();
bool color = font2->IsColorFont();
qCDebug(lcQpaFonts) << "Family" << familyName << "has english variant" << englishLocaleStyleName << ", in default locale:" << defaultLocaleStyleName << stretch << style << weight << fixed;
@ -190,6 +191,7 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(*face, englishLocaleFamilyName));
}
@ -205,6 +207,7 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(*face, defaultLocaleFamilyName));
}
@ -290,18 +293,20 @@ bool QWindowsDirectWriteFontDatabase::populateFamilyAliases(const QString &missi
DirectWriteScope<IDWriteFont> font;
if (SUCCEEDED(fontCollection->GetFontFromFontFace(*directWriteFontFace, &font))) {
DirectWriteScope<IDWriteFont1> font1;
if (SUCCEEDED(font->QueryInterface(__uuidof(IDWriteFont1),
reinterpret_cast<void **>(&font1)))) {
DirectWriteScope<IDWriteFont2> font2;
if (SUCCEEDED(font->QueryInterface(__uuidof(IDWriteFont2),
reinterpret_cast<void **>(&font2)))) {
DirectWriteScope<IDWriteLocalizedStrings> names;
if (SUCCEEDED(font1->GetFaceNames(&names))) {
if (SUCCEEDED(font2->GetFaceNames(&names))) {
wchar_t englishLocale[] = L"en-us";
QString englishLocaleStyleName = localeString(*names, englishLocale);
QFont::Stretch stretch = fromDirectWriteStretch(font1->GetStretch());
QFont::Style style = fromDirectWriteStyle(font1->GetStyle());
QFont::Weight weight = fromDirectWriteWeight(font1->GetWeight());
bool fixed = font1->IsMonospacedFont();
QFont::Stretch stretch = fromDirectWriteStretch(font2->GetStretch());
QFont::Style style = fromDirectWriteStyle(font2->GetStyle());
QFont::Weight weight = fromDirectWriteWeight(font2->GetWeight());
bool fixed = font2->IsMonospacedFont();
bool isColorFont = font2->IsColorFont();
QSupportedWritingSystems writingSystems = supportedWritingSystems(*directWriteFontFace);
@ -316,6 +321,7 @@ bool QWindowsDirectWriteFontDatabase::populateFamilyAliases(const QString &missi
true,
0xffff,
fixed,
isColorFont,
writingSystems,
new FontHandle(*directWriteFontFace, missingFamily));
@ -407,9 +413,13 @@ QFontEngine *QWindowsDirectWriteFontDatabase::fontEngine(const QFontDef &fontDef
return fontEngine;
}
QStringList QWindowsDirectWriteFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
QStringList QWindowsDirectWriteFontDatabase::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const
{
QStringList result;
result.append(QWindowsFontDatabaseBase::familiesForScript(script));
result.append(familyForStyleHint(styleHint));
result.append(extraTryFontsForFamily(family));
result.append(QPlatformFontDatabase::fallbacksForFamily(family, style, styleHint, script));
@ -516,6 +526,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
QFont::Style style = fromDirectWriteStyle(face3->GetStyle());
QFont::Weight weight = fromDirectWriteWeight(face3->GetWeight());
bool fixed = face3->IsMonospacedFont();
bool color = face3->IsColorFont();
qCDebug(lcQpaFonts) << "\tFont names:" << englishLocaleFamilyName << ", " << defaultLocaleFamilyName
<< ", style names:" << englishLocaleStyleName << ", " << defaultLocaleStyleName
@ -545,6 +556,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(face, englishLocaleFamilyName));
}
@ -570,6 +582,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(face, defaultLocaleFamilyName));
}
@ -595,6 +608,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(face, englishLocaleGdiCompatibleFamilyName));
}
@ -620,6 +634,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(face, defaultLocaleGdiCompatibleFamilyName));
}
@ -645,6 +660,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(face, englishLocaleTypographicFamilyName));
}
@ -670,6 +686,7 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
scalable,
size,
fixed,
color,
writingSystems,
new FontHandle(face, defaultLocaleTypographicFamilyName));
}

View File

@ -43,7 +43,10 @@ public:
bool populateFamilyAliases(const QString &missingFamily) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const override;
QStringList fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const override;
QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *font = nullptr) override;
bool isPrivateFontFamily(const QString &family) const override;

View File

@ -541,19 +541,19 @@ static bool addFontToDatabase(QString familyName,
const bool wasPopulated = QPlatformFontDatabase::isFamilyPopulated(familyName);
QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight,
style, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
style, stretch, antialias, scalable, size, fixed, false, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
// add fonts windows can generate for us:
if (weight <= QFont::DemiBold && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold,
style, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
style, stretch, antialias, scalable, size, fixed, false, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
if (style != QFont::StyleItalic && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, weight,
QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
QFont::StyleItalic, stretch, antialias, scalable, size, fixed, false, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
if (weight <= QFont::DemiBold && style != QFont::StyleItalic && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold,
QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
QFont::StyleItalic, stretch, antialias, scalable, size, fixed, false, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
// We came here from populating a different font family, so we have
// to ensure the entire typographic family is populated before we
@ -567,7 +567,7 @@ static bool addFontToDatabase(QString familyName,
if (!subFamilyName.isEmpty() && familyName != subFamilyName) {
QPlatformFontDatabase::registerFont(subFamilyName, subFamilyStyle, foundryName, weight,
style, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
style, stretch, antialias, scalable, size, fixed, false, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
}
if (!englishName.isEmpty() && englishName != familyName)
@ -1162,9 +1162,13 @@ void QWindowsFontDatabase::refUniqueFont(const QString &uniqueFont)
++it->refCount;
}
QStringList QWindowsFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
QStringList QWindowsFontDatabase::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const
{
QStringList result;
result.append(QWindowsFontDatabaseBase::familiesForScript(script));
result.append(familyForStyleHint(styleHint));
result.append(m_eudcFonts);
result.append(extraTryFontsForFamily(family));

View File

@ -243,24 +243,24 @@ static bool addFontToDatabase(QString familyName,
value.prepend(QFile::decodeName(qgetenv("windir") + "\\Fonts\\"));
QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight, style, stretch,
antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
antialias, scalable, size, fixed, false, writingSystems, createFontFile(value, index));
// add fonts windows can generate for us:
if (weight <= QFont::DemiBold && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold, style, stretch,
antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
antialias, scalable, size, fixed, false, writingSystems, createFontFile(value, index));
if (style != QFont::StyleItalic && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, weight, QFont::StyleItalic, stretch,
antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
antialias, scalable, size, fixed, false, writingSystems, createFontFile(value, index));
if (weight <= QFont::DemiBold && style != QFont::StyleItalic && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold, QFont::StyleItalic, stretch,
antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
antialias, scalable, size, fixed, false, writingSystems, createFontFile(value, index));
if (!subFamilyName.isEmpty() && familyName != subFamilyName) {
QPlatformFontDatabase::registerFont(subFamilyName, subFamilyStyle, foundryName, weight,
style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
style, stretch, antialias, scalable, size, fixed, false, writingSystems, createFontFile(value, index));
}
if (!englishName.isEmpty() && englishName != familyName)
@ -397,9 +397,13 @@ QFontEngine *QWindowsFontDatabaseFT::fontEngine(const QByteArray &fontData, qrea
return fe;
}
QStringList QWindowsFontDatabaseFT::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
QStringList QWindowsFontDatabaseFT::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QFontDatabasePrivate::ExtendedScript script) const
{
QStringList result;
result.append(QWindowsFontDatabaseBase::familiesForScript(script));
result.append(QWindowsFontDatabaseBase::familyForStyleHint(styleHint));
result.append(QWindowsFontDatabaseBase::extraTryFontsForFamily(family));
result.append(QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script));

View File

@ -33,7 +33,7 @@ public:
QStringList fallbacksForFamily(const QString &family, QFont::Style style,
QFont::StyleHint styleHint,
QChar::Script script) const override;
QFontDatabasePrivate::ExtendedScript script) const override;
QString fontDir() const override;
QFont defaultFont() const override;

View File

@ -51,7 +51,7 @@ public:
bool populateFamilyAliases(const QString &missingFamily) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QFontDatabasePrivate::ExtendedScript script) const override;
QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override;
void releaseHandle(void *handle) override;
QString fontDir() const override;

View File

@ -873,6 +873,14 @@ QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QByteArray &fontData, qr
return fontEngine;
}
QStringList QWindowsFontDatabaseBase::familiesForScript(QFontDatabasePrivate::ExtendedScript script)
{
if (script == QFontDatabasePrivate::Script_Emoji)
return QStringList{} << QStringLiteral("Segoe UI Emoji");
else
return QStringList{};
}
QString QWindowsFontDatabaseBase::familyForStyleHint(QFont::StyleHint styleHint)
{
switch (styleHint) {

View File

@ -75,6 +75,7 @@ public:
static QString familyForStyleHint(QFont::StyleHint styleHint);
static QStringList extraTryFontsForFamily(const QString &family);
static QStringList familiesForScript(QFontDatabasePrivate::ExtendedScript script);
class FontTable{};
class EmbeddedFont

View File

@ -45,10 +45,15 @@ void QAndroidPlatformFontDatabase::populateFontDatabase()
QStringList QAndroidPlatformFontDatabase::fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QChar::Script script) const
QFontDatabasePrivate::ExtendedScript script) const
{
QStringList result;
if (script == QFontDatabasePrivate::Script_Emoji) {
result.append(QStringLiteral("Noto Color Emoji"));
result.append(QStringLiteral("Noto Color Emoji Flags"));
}
// Prepend CJK fonts by the locale.
QLocale locale = QLocale::system();
switch (locale.language()) {

View File

@ -17,7 +17,7 @@ public:
QStringList fallbacksForFamily(const QString &family,
QFont::Style style,
QFont::StyleHint styleHint,
QChar::Script script) const override;
QFontDatabasePrivate::ExtendedScript script) const override;
};
QT_END_NAMESPACE

View File

@ -319,7 +319,7 @@ QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle
QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
QFont::StyleHint styleHint,
QChar::Script script) const
QFontDatabasePrivate::ExtendedScript script) const
{
QStringList fallbacks
= QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);

View File

@ -20,7 +20,7 @@ public:
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style,
QFont::StyleHint styleHint,
QChar::Script script) const override;
QFontDatabasePrivate::ExtendedScript script) const override;
void releaseHandle(void *handle) override;
QFont defaultFont() const override;

View File

@ -49,6 +49,7 @@ set(testdata_resource_files
"LED_REAL.TTF"
"QtTestLimitedFont-Regular.ttf"
"QtTestFallbackFont-Regular.ttf"
"QtEmojiTestFont-Regular.ttf"
)
qt_internal_add_resource(tst_qfontdatabase "testdata"

View File

@ -71,6 +71,7 @@ private slots:
#endif
void addApplicationFontFallback();
void addApplicationEmojiFontFamily();
private:
QString m_ledFont;
@ -80,6 +81,7 @@ private:
QString m_testFontVariable;
QString m_limitedFont;
QString m_fallbackFont;
QString m_emojiFont;
};
tst_QFontDatabase::tst_QFontDatabase()
@ -95,6 +97,7 @@ void tst_QFontDatabase::initTestCase()
m_testFontVariable = QFINDTESTDATA("testfont_variable.ttf");
m_limitedFont = QFINDTESTDATA("QtTestLimitedFont-Regular.ttf");
m_fallbackFont = QFINDTESTDATA("QtTestFallbackFont-Regular.ttf");
m_emojiFont = QFINDTESTDATA("QtEmojiTestFont-Regular.ttf");
QVERIFY(!m_ledFont.isEmpty());
QVERIFY(!m_testFont.isEmpty());
QVERIFY(!m_testFontCondensed.isEmpty());
@ -102,6 +105,7 @@ void tst_QFontDatabase::initTestCase()
QVERIFY(!m_testFontVariable.isEmpty());
QVERIFY(!m_limitedFont.isEmpty());
QVERIFY(!m_fallbackFont.isEmpty());
QVERIFY(!m_emojiFont.isEmpty());
}
void tst_QFontDatabase::styles_data()
@ -763,5 +767,65 @@ void tst_QFontDatabase::addApplicationFontFallback()
QVERIFY(QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script_Latin, u"QtTestFallbackFont"_s));
}
void tst_QFontDatabase::addApplicationEmojiFontFamily()
{
int id = -1;
auto cleanup = qScopeGuard([&id] {
if (id >= 0)
QFontDatabase::removeApplicationFont(id);
});
id = QFontDatabase::addApplicationFont(m_emojiFont);
QVERIFY(id >= 0);
QStringList families = QFontDatabase::applicationFontFamilies(id);
QVERIFY(families.size() > 0);
const QChar airplane(0x2708);
const QChar vs16(0xfe0f);
QFontDatabase::addApplicationEmojiFontFamily(families.first());
// Get emoji version of regular airplane symbol
{
QTextLayout layout;
layout.setText(QString(airplane) + vs16);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns();
QCOMPARE(glyphRuns.size(), 1);
QGlyphRun glyphRun = glyphRuns.first();
QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
QCOMPARE(glyphIndexes.size(), 1);
QCOMPARE(glyphIndexes.at(0), 237);
}
const QChar asterisk('*');
const QChar enclosingKeyCap(0x20e3);
// Get emoji keycap ligature (vs16 should be ignored when evaluating ligature substitution)
{
QTextLayout layout;
layout.setText(QString(asterisk) + vs16 + enclosingKeyCap);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns();
QCOMPARE(glyphRuns.size(), 1);
QGlyphRun glyphRun = glyphRuns.first();
QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
QCOMPARE(glyphIndexes.size(), 1);
QCOMPARE(glyphIndexes.at(0), 238);
}
}
QTEST_MAIN(tst_QFontDatabase)
#include "tst_qfontdatabase.moc"

View File

@ -0,0 +1,34 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(emojisequences LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
qt_standard_project_setup()
qt_internal_add_manual_test(emojisequences
GUI
SOURCES
main.cpp
mainwindow.h mainwindow.cpp
mainwindow.ui
LIBRARIES
Qt::Gui
Qt::Widgets
ENABLE_AUTOGEN_TOOLS
uic
)
set(emojisequences_resource_files
"emoji-test.txt"
)
qt_internal_add_resource(emojisequences "emojisequences"
PREFIX
"/"
FILES
${emojisequences_resource_files}
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "mainwindow.h"
#include <QtGui>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@ -0,0 +1,95 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->toolButton, &QToolButton::clicked, this, &MainWindow::loadCustomFont);
populateEmojiTest();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::populateEmojiTest()
{
QFile file(":/emoji-test.txt");
ui->tableWidget->clear();
ui->tableWidget->setColumnCount(8);
QList<QPair<QString, QString> > strings;
if (file.open(QIODevice::ReadOnly)) {
while (!file.atEnd()) {
QString l = file.readLine();
QStringList toolTip;
QString testString;
QStringList tokens = l.split(QLatin1Char(' '), Qt::SkipEmptyParts);
for (int i = 0; i < tokens.size(); ++i) {
if (tokens.at(i) == QLatin1Char(';'))
break;
bool ok;
char32_t ucs4 = tokens.at(i).toUInt(&ok, 16);
if (!ok)
break;
testString += QString::fromUcs4(&ucs4, 1);
toolTip << QString::number(ucs4, 16);
}
if (!toolTip.isEmpty()) {
strings.append(qMakePair(testString, toolTip.join(',')));
}
}
}
ui->tableWidget->setRowCount(strings.count() / 8);
for (int i = 0; i < strings.count(); ++i) {
int row = i / 8;
int column = i % 8;
QString testString = strings.at(i).first;
QString toolTip = strings.at(i).second;
QTableWidgetItem *it = new QTableWidgetItem(testString);
ui->tableWidget->setItem(row, column, it);
it->setText(testString);
it->setToolTip(toolTip);
}
}
void MainWindow::loadCustomFont()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
ui->tableWidget->clear();
QFontDatabase::removeAllApplicationFonts();
QString fileName = QFileDialog::getOpenFileName(this, tr("Open font file"), QString(), tr("Fonts (*.ttf *.otf);All files (*)"));
if (!fileName.isEmpty()) {
int id = QFontDatabase::addApplicationFont(fileName);
if (id >= 0) {
QStringList families = QFontDatabase::applicationFontFamilies(id);
QString family = families.size() > 0 ? families.first() : QString();
if (!family.isEmpty()) {
QFontDatabase::setApplicationEmojiFontFamilies(QStringList() << family);
populateEmojiTest();
}
}
}
#endif
}

View File

@ -0,0 +1,31 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void loadCustomFont();
private:
void populateEmojiTest();
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1245</width>
<height>972</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="tableWidget">
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton">
<property name="text">
<string>Load custom font...</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1245</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>