qt6-qtbase/qtbase-use-emoji-segmenter-to-apply-emoji-fonts-automatically.patch
Jan Grulich 5a09932501 Backport additional fixes for emoji support
Resolves: RHEL-4218
2024-12-16 12:28:14 +01:00

5918 lines
460 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 16850709306589a2433c0038605d365a6b6bedad Mon Sep 17 00:00:00 2001
From: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Date: Tue, 2 Apr 2024 13:20:34 +0200
Subject: 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>
---
diff --git a/src/gui/text/coretext/qcoretextfontdatabase.mm b/src/gui/text/coretext/qcoretextfontdatabase.mm
index 2197c83f..fc551cdb 100644
--- a/src/gui/text/coretext/qcoretextfontdatabase.mm
+++ b/src/gui/text/coretext/qcoretextfontdatabase.mm
@@ -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);
-
- fallbackList.append(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);
+ }
+
+ // 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;
diff --git a/src/gui/text/coretext/qcoretextfontdatabase_p.h b/src/gui/text/coretext/qcoretextfontdatabase_p.h
index eeea9ad6..5c8a6ad3 100644
--- a/src/gui/text/coretext/qcoretextfontdatabase_p.h
+++ b/src/gui/text/coretext/qcoretextfontdatabase_p.h
@@ -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;
diff --git a/src/gui/text/freetype/qfreetypefontdatabase.cpp b/src/gui/text/freetype/qfreetypefontdatabase.cpp
index 018e590a..8e0aec1f 100644
--- a/src/gui/text/freetype/qfreetypefontdatabase.cpp
+++ b/src/gui/text/freetype/qfreetypefontdatabase.cpp
@@ -103,6 +103,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)
@@ -183,6 +184,7 @@ void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_,
true,
0,
fixedPitch,
+ isColor,
writingSystems,
variantFontFile);
}
@@ -224,6 +226,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;
@@ -337,9 +345,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);
diff --git a/src/gui/text/freetype/qfreetypefontdatabase_p.h b/src/gui/text/freetype/qfreetypefontdatabase_p.h
index 5fcec585..bc5cdbff 100644
--- a/src/gui/text/freetype/qfreetypefontdatabase_p.h
+++ b/src/gui/text/freetype/qfreetypefontdatabase_p.h
@@ -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);
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index c8881a9b..94343854 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -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];
@@ -2683,8 +2683,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
@@ -2696,8 +2698,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();
@@ -3394,7 +3398,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);
diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h
index b674e711..844ac5fc 100644
--- a/src/gui/text/qfont_p.h
+++ b/src/gui/text/qfont_p.h
@@ -23,6 +23,7 @@
#include "QtCore/qstringlist.h"
#include <QtGui/qfontdatabase.h>
#include "private/qfixed_p.h"
+#include "private/qfontdatabase_p.h"
QT_BEGIN_NAMESPACE
@@ -152,8 +153,7 @@ public:
QAtomicInt ref;
const int fontCacheId;
- QFontEngine *engines[QChar::ScriptCount];
-
+ QFontEngine *engines[QFontDatabasePrivate::ScriptCount];
private:
Q_DISABLE_COPY_MOVE(QFontEngineData)
};
diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp
index 28539977..96c2337e 100644
--- a/src/gui/text/qfontdatabase.cpp
+++ b/src/gui/text/qfontdatabase.cpp
@@ -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;
}
diff --git a/src/gui/text/qfontdatabase.h b/src/gui/text/qfontdatabase.h
index 91a53426..cd5cc972 100644
--- a/src/gui/text/qfontdatabase.h
+++ b/src/gui/text/qfontdatabase.h
@@ -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);
};
diff --git a/src/gui/text/qfontdatabase_p.h b/src/gui/text/qfontdatabase_p.h
index 38e1b4ad..55fa1317 100644
--- a/src/gui/text/qfontdatabase_p.h
+++ b/src/gui/text/qfontdatabase_p.h
@@ -29,7 +29,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
@@ -151,6 +151,7 @@ struct Q_GUI_EXPORT QtFontFamily
:
populated(false),
fixedPitch(false),
+ colorFont(false),
name(n), count(0), foundries(nullptr)
{
memset(writingSystems, 0, sizeof(writingSystems));
@@ -163,6 +164,7 @@ struct Q_GUI_EXPORT QtFontFamily
bool populated : 1;
bool fixedPitch : 1;
+ bool colorFont : 1;
QString name;
QStringList aliases;
@@ -198,13 +200,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 {
@@ -232,21 +242,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,
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp
index f5aec978..2e5c0624 100644
--- a/src/gui/text/qfontengine.cpp
+++ b/src/gui/text/qfontengine.cpp
@@ -1734,7 +1734,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()
{
@@ -1744,7 +1744,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)
@@ -1792,7 +1792,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;
@@ -2363,7 +2363,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);
diff --git a/src/gui/text/qplatformfontdatabase.cpp b/src/gui/text/qplatformfontdatabase.cpp
index a146254f..3d3f3c30 100644
--- a/src/gui/text/qplatformfontdatabase.cpp
+++ b/src/gui/text/qplatformfontdatabase.cpp
@@ -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);
}
diff --git a/src/gui/text/qplatformfontdatabase.h b/src/gui/text/qplatformfontdatabase.h
index 3007a118..ba6dfac4 100644
--- a/src/gui/text/qplatformfontdatabase.h
+++ b/src/gui/text/qplatformfontdatabase.h
@@ -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);
diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp
index 08512bea..0f10f4a7 100644
--- a/src/gui/text/qtextengine.cpp
+++ b/src/gui/text/qtextengine.cpp
@@ -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;
@@ -1615,7 +1615,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
@@ -1919,8 +1921,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;
@@ -1950,9 +1982,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:
{
@@ -1992,7 +2091,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;
}
diff --git a/src/gui/text/unix/qfontconfigdatabase.cpp b/src/gui/text/unix/qfontconfigdatabase.cpp
index f6c59380..7866e341 100644
--- a/src/gui/text/unix/qfontconfigdatabase.cpp
+++ b/src/gui/text/unix/qfontconfigdatabase.cpp
@@ -477,6 +477,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();
@@ -492,7 +498,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,
@@ -502,6 +508,7 @@ static void populateFromPattern(FcPattern *pattern,
stretch,
style,
fixedPitch,
+ colorFont,
writingSystems,
QByteArray((const char*)file_value),
applicationFont->data);
@@ -538,7 +545,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);
}
@@ -615,9 +622,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;
}
@@ -636,7 +643,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);
}
@@ -760,7 +767,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();
@@ -773,6 +783,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;
@@ -780,8 +798,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);
diff --git a/src/gui/text/unix/qfontconfigdatabase_p.h b/src/gui/text/unix/qfontconfigdatabase_p.h
index dd7a70a3..40de8919 100644
--- a/src/gui/text/unix/qfontconfigdatabase_p.h
+++ b/src/gui/text/unix/qfontconfigdatabase_p.h
@@ -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;
diff --git a/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp b/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp
index 610cd1d6..f2671b6c 100644
--- a/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp
+++ b/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp
@@ -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));
}
diff --git a/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h b/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h
index 3e5e9663..1ea01cbd 100644
--- a/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h
+++ b/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h
@@ -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;
diff --git a/src/gui/text/windows/qwindowsfontdatabase.cpp b/src/gui/text/windows/qwindowsfontdatabase.cpp
index 772620d6..73814c6a 100644
--- a/src/gui/text/windows/qwindowsfontdatabase.cpp
+++ b/src/gui/text/windows/qwindowsfontdatabase.cpp
@@ -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));
diff --git a/src/gui/text/windows/qwindowsfontdatabase_ft.cpp b/src/gui/text/windows/qwindowsfontdatabase_ft.cpp
index 0604a85e..18d90261 100644
--- a/src/gui/text/windows/qwindowsfontdatabase_ft.cpp
+++ b/src/gui/text/windows/qwindowsfontdatabase_ft.cpp
@@ -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));
diff --git a/src/gui/text/windows/qwindowsfontdatabase_ft_p.h b/src/gui/text/windows/qwindowsfontdatabase_ft_p.h
index 381a7be4..1b8fb9dd 100644
--- a/src/gui/text/windows/qwindowsfontdatabase_ft_p.h
+++ b/src/gui/text/windows/qwindowsfontdatabase_ft_p.h
@@ -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;
diff --git a/src/gui/text/windows/qwindowsfontdatabase_p.h b/src/gui/text/windows/qwindowsfontdatabase_p.h
index 0c99c91f..92628726 100644
--- a/src/gui/text/windows/qwindowsfontdatabase_p.h
+++ b/src/gui/text/windows/qwindowsfontdatabase_p.h
@@ -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;
diff --git a/src/gui/text/windows/qwindowsfontdatabasebase.cpp b/src/gui/text/windows/qwindowsfontdatabasebase.cpp
index 84e619b0..fdaf9240 100644
--- a/src/gui/text/windows/qwindowsfontdatabasebase.cpp
+++ b/src/gui/text/windows/qwindowsfontdatabasebase.cpp
@@ -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) {
diff --git a/src/gui/text/windows/qwindowsfontdatabasebase_p.h b/src/gui/text/windows/qwindowsfontdatabasebase_p.h
index 55a33635..beb9b52f 100644
--- a/src/gui/text/windows/qwindowsfontdatabasebase_p.h
+++ b/src/gui/text/windows/qwindowsfontdatabasebase_p.h
@@ -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
diff --git a/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp b/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
index 82a10dac..3c174f04 100644
--- a/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
+++ b/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
@@ -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()) {
diff --git a/src/plugins/platforms/android/qandroidplatformfontdatabase.h b/src/plugins/platforms/android/qandroidplatformfontdatabase.h
index 23561e22..1150a298 100644
--- a/src/plugins/platforms/android/qandroidplatformfontdatabase.h
+++ b/src/plugins/platforms/android/qandroidplatformfontdatabase.h
@@ -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
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
index 3f3dc10f..e1882dd1 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
@@ -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);
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h
index a1c8f1ff..aea3b955 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.h
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h
@@ -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;
diff --git a/tests/auto/gui/text/qfontdatabase/CMakeLists.txt b/tests/auto/gui/text/qfontdatabase/CMakeLists.txt
index 0cb6e8d7..03e4bdce 100644
--- a/tests/auto/gui/text/qfontdatabase/CMakeLists.txt
+++ b/tests/auto/gui/text/qfontdatabase/CMakeLists.txt
@@ -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"
diff --git a/tests/auto/gui/text/qfontdatabase/QtEmojiTestFont-Regular.ttf b/tests/auto/gui/text/qfontdatabase/QtEmojiTestFont-Regular.ttf
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp
index 8733f64d..5773363d 100644
--- a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp
+++ b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp
@@ -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()
@@ -760,5 +764,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"
diff --git a/tests/manual/emojisequences/CMakeLists.txt b/tests/manual/emojisequences/CMakeLists.txt
new file mode 100644
index 00000000..c367bb12
--- /dev/null
+++ b/tests/manual/emojisequences/CMakeLists.txt
@@ -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}
+)
diff --git a/tests/manual/emojisequences/emoji-test.txt b/tests/manual/emojisequences/emoji-test.txt
new file mode 100644
index 00000000..3357ccf8
--- /dev/null
+++ b/tests/manual/emojisequences/emoji-test.txt
@@ -0,0 +1,3768 @@
+1F600 ; fully-qualified # 😀 grinning face
+1F601 ; fully-qualified # 😁 beaming face with smiling eyes
+1F602 ; fully-qualified # 😂 face with tears of joy
+1F923 ; fully-qualified # 🤣 rolling on the floor laughing
+1F603 ; fully-qualified # 😃 grinning face with big eyes
+1F604 ; fully-qualified # 😄 grinning face with smiling eyes
+1F605 ; fully-qualified # 😅 grinning face with sweat
+1F606 ; fully-qualified # 😆 grinning squinting face
+1F609 ; fully-qualified # 😉 winking face
+1F60A ; fully-qualified # 😊 smiling face with smiling eyes
+1F60B ; fully-qualified # 😋 face savoring food
+1F60E ; fully-qualified # 😎 smiling face with sunglasses
+1F60D ; fully-qualified # 😍 smiling face with heart-eyes
+1F618 ; fully-qualified # 😘 face blowing a kiss
+1F970 ; fully-qualified # 🥰 smiling face with 3 hearts
+1F617 ; fully-qualified # 😗 kissing face
+1F619 ; fully-qualified # 😙 kissing face with smiling eyes
+1F61A ; fully-qualified # 😚 kissing face with closed eyes
+263A FE0F ; fully-qualified # ☺️ smiling face
+263A ; non-fully-qualified # ☺ smiling face
+1F642 ; fully-qualified # 🙂 slightly smiling face
+1F917 ; fully-qualified # 🤗 hugging face
+1F929 ; fully-qualified # 🤩 star-struck
+
+# subgroup: face-neutral
+1F914 ; fully-qualified # 🤔 thinking face
+1F928 ; fully-qualified # 🤨 face with raised eyebrow
+1F610 ; fully-qualified # 😐 neutral face
+1F611 ; fully-qualified # 😑 expressionless face
+1F636 ; fully-qualified # 😶 face without mouth
+1F644 ; fully-qualified # 🙄 face with rolling eyes
+1F60F ; fully-qualified # 😏 smirking face
+1F623 ; fully-qualified # 😣 persevering face
+1F625 ; fully-qualified # 😥 sad but relieved face
+1F62E ; fully-qualified # 😮 face with open mouth
+1F910 ; fully-qualified # 🤐 zipper-mouth face
+1F62F ; fully-qualified # 😯 hushed face
+1F62A ; fully-qualified # 😪 sleepy face
+1F62B ; fully-qualified # 😫 tired face
+1F634 ; fully-qualified # 😴 sleeping face
+1F60C ; fully-qualified # 😌 relieved face
+1F61B ; fully-qualified # 😛 face with tongue
+1F61C ; fully-qualified # 😜 winking face with tongue
+1F61D ; fully-qualified # 😝 squinting face with tongue
+1F924 ; fully-qualified # 🤤 drooling face
+1F612 ; fully-qualified # 😒 unamused face
+1F613 ; fully-qualified # 😓 downcast face with sweat
+1F614 ; fully-qualified # 😔 pensive face
+1F615 ; fully-qualified # 😕 confused face
+1F643 ; fully-qualified # 🙃 upside-down face
+1F911 ; fully-qualified # 🤑 money-mouth face
+1F632 ; fully-qualified # 😲 astonished face
+
+# subgroup: face-negative
+2639 FE0F ; fully-qualified # ☹️ frowning face
+2639 ; non-fully-qualified # ☹ frowning face
+1F641 ; fully-qualified # 🙁 slightly frowning face
+1F616 ; fully-qualified # 😖 confounded face
+1F61E ; fully-qualified # 😞 disappointed face
+1F61F ; fully-qualified # 😟 worried face
+1F624 ; fully-qualified # 😤 face with steam from nose
+1F622 ; fully-qualified # 😢 crying face
+1F62D ; fully-qualified # 😭 loudly crying face
+1F626 ; fully-qualified # 😦 frowning face with open mouth
+1F627 ; fully-qualified # 😧 anguished face
+1F628 ; fully-qualified # 😨 fearful face
+1F629 ; fully-qualified # 😩 weary face
+1F92F ; fully-qualified # 🤯 exploding head
+1F62C ; fully-qualified # 😬 grimacing face
+1F630 ; fully-qualified # 😰 anxious face with sweat
+1F631 ; fully-qualified # 😱 face screaming in fear
+1F975 ; fully-qualified # 🥵 hot face
+1F976 ; fully-qualified # 🥶 cold face
+1F633 ; fully-qualified # 😳 flushed face
+1F92A ; fully-qualified # 🤪 zany face
+1F635 ; fully-qualified # 😵 dizzy face
+1F621 ; fully-qualified # 😡 pouting face
+1F620 ; fully-qualified # 😠 angry face
+1F92C ; fully-qualified # 🤬 face with symbols on mouth
+
+# subgroup: face-sick
+1F637 ; fully-qualified # 😷 face with medical mask
+1F912 ; fully-qualified # 🤒 face with thermometer
+1F915 ; fully-qualified # 🤕 face with head-bandage
+1F922 ; fully-qualified # 🤢 nauseated face
+1F92E ; fully-qualified # 🤮 face vomiting
+1F927 ; fully-qualified # 🤧 sneezing face
+
+# subgroup: face-role
+1F607 ; fully-qualified # 😇 smiling face with halo
+1F920 ; fully-qualified # 🤠 cowboy hat face
+1F973 ; fully-qualified # 🥳 partying face
+1F974 ; fully-qualified # 🥴 woozy face
+1F97A ; fully-qualified # 🥺 pleading face
+1F925 ; fully-qualified # 🤥 lying face
+1F92B ; fully-qualified # 🤫 shushing face
+1F92D ; fully-qualified # 🤭 face with hand over mouth
+1F9D0 ; fully-qualified # 🧐 face with monocle
+1F913 ; fully-qualified # 🤓 nerd face
+
+# subgroup: face-fantasy
+1F608 ; fully-qualified # 😈 smiling face with horns
+1F47F ; fully-qualified # 👿 angry face with horns
+1F921 ; fully-qualified # 🤡 clown face
+1F479 ; fully-qualified # 👹 ogre
+1F47A ; fully-qualified # 👺 goblin
+1F480 ; fully-qualified # 💀 skull
+2620 FE0F ; fully-qualified # ☠️ skull and crossbones
+2620 ; non-fully-qualified # ☠ skull and crossbones
+1F47B ; fully-qualified # 👻 ghost
+1F47D ; fully-qualified # 👽 alien
+1F47E ; fully-qualified # 👾 alien monster
+1F916 ; fully-qualified # 🤖 robot face
+1F4A9 ; fully-qualified # 💩 pile of poo
+
+# subgroup: cat-face
+1F63A ; fully-qualified # 😺 grinning cat face
+1F638 ; fully-qualified # 😸 grinning cat face with smiling eyes
+1F639 ; fully-qualified # 😹 cat face with tears of joy
+1F63B ; fully-qualified # 😻 smiling cat face with heart-eyes
+1F63C ; fully-qualified # 😼 cat face with wry smile
+1F63D ; fully-qualified # 😽 kissing cat face
+1F640 ; fully-qualified # 🙀 weary cat face
+1F63F ; fully-qualified # 😿 crying cat face
+1F63E ; fully-qualified # 😾 pouting cat face
+
+# subgroup: monkey-face
+1F648 ; fully-qualified # 🙈 see-no-evil monkey
+1F649 ; fully-qualified # 🙉 hear-no-evil monkey
+1F64A ; fully-qualified # 🙊 speak-no-evil monkey
+
+# subgroup: skin-tone
+1F3FB ; fully-qualified # 🏻 light skin tone
+1F3FC ; fully-qualified # 🏼 medium-light skin tone
+1F3FD ; fully-qualified # 🏽 medium skin tone
+1F3FE ; fully-qualified # 🏾 medium-dark skin tone
+1F3FF ; fully-qualified # 🏿 dark skin tone
+
+# subgroup: person
+1F476 ; fully-qualified # 👶 baby
+1F476 1F3FB ; fully-qualified # 👶🏻 baby: light skin tone
+1F476 1F3FC ; fully-qualified # 👶🏼 baby: medium-light skin tone
+1F476 1F3FD ; fully-qualified # 👶🏽 baby: medium skin tone
+1F476 1F3FE ; fully-qualified # 👶🏾 baby: medium-dark skin tone
+1F476 1F3FF ; fully-qualified # 👶🏿 baby: dark skin tone
+1F9D2 ; fully-qualified # 🧒 child
+1F9D2 1F3FB ; fully-qualified # 🧒🏻 child: light skin tone
+1F9D2 1F3FC ; fully-qualified # 🧒🏼 child: medium-light skin tone
+1F9D2 1F3FD ; fully-qualified # 🧒🏽 child: medium skin tone
+1F9D2 1F3FE ; fully-qualified # 🧒🏾 child: medium-dark skin tone
+1F9D2 1F3FF ; fully-qualified # 🧒🏿 child: dark skin tone
+1F466 ; fully-qualified # 👦 boy
+1F466 1F3FB ; fully-qualified # 👦🏻 boy: light skin tone
+1F466 1F3FC ; fully-qualified # 👦🏼 boy: medium-light skin tone
+1F466 1F3FD ; fully-qualified # 👦🏽 boy: medium skin tone
+1F466 1F3FE ; fully-qualified # 👦🏾 boy: medium-dark skin tone
+1F466 1F3FF ; fully-qualified # 👦🏿 boy: dark skin tone
+1F467 ; fully-qualified # 👧 girl
+1F467 1F3FB ; fully-qualified # 👧🏻 girl: light skin tone
+1F467 1F3FC ; fully-qualified # 👧🏼 girl: medium-light skin tone
+1F467 1F3FD ; fully-qualified # 👧🏽 girl: medium skin tone
+1F467 1F3FE ; fully-qualified # 👧🏾 girl: medium-dark skin tone
+1F467 1F3FF ; fully-qualified # 👧🏿 girl: dark skin tone
+1F9D1 ; fully-qualified # 🧑 adult
+1F9D1 1F3FB ; fully-qualified # 🧑🏻 adult: light skin tone
+1F9D1 1F3FC ; fully-qualified # 🧑🏼 adult: medium-light skin tone
+1F9D1 1F3FD ; fully-qualified # 🧑🏽 adult: medium skin tone
+1F9D1 1F3FE ; fully-qualified # 🧑🏾 adult: medium-dark skin tone
+1F9D1 1F3FF ; fully-qualified # 🧑🏿 adult: dark skin tone
+1F468 ; fully-qualified # 👨 man
+1F468 1F3FB ; fully-qualified # 👨🏻 man: light skin tone
+1F468 1F3FC ; fully-qualified # 👨🏼 man: medium-light skin tone
+1F468 1F3FD ; fully-qualified # 👨🏽 man: medium skin tone
+1F468 1F3FE ; fully-qualified # 👨🏾 man: medium-dark skin tone
+1F468 1F3FF ; fully-qualified # 👨🏿 man: dark skin tone
+1F469 ; fully-qualified # 👩 woman
+1F469 1F3FB ; fully-qualified # 👩🏻 woman: light skin tone
+1F469 1F3FC ; fully-qualified # 👩🏼 woman: medium-light skin tone
+1F469 1F3FD ; fully-qualified # 👩🏽 woman: medium skin tone
+1F469 1F3FE ; fully-qualified # 👩🏾 woman: medium-dark skin tone
+1F469 1F3FF ; fully-qualified # 👩🏿 woman: dark skin tone
+1F9D3 ; fully-qualified # 🧓 older adult
+1F9D3 1F3FB ; fully-qualified # 🧓🏻 older adult: light skin tone
+1F9D3 1F3FC ; fully-qualified # 🧓🏼 older adult: medium-light skin tone
+1F9D3 1F3FD ; fully-qualified # 🧓🏽 older adult: medium skin tone
+1F9D3 1F3FE ; fully-qualified # 🧓🏾 older adult: medium-dark skin tone
+1F9D3 1F3FF ; fully-qualified # 🧓🏿 older adult: dark skin tone
+1F474 ; fully-qualified # 👴 old man
+1F474 1F3FB ; fully-qualified # 👴🏻 old man: light skin tone
+1F474 1F3FC ; fully-qualified # 👴🏼 old man: medium-light skin tone
+1F474 1F3FD ; fully-qualified # 👴🏽 old man: medium skin tone
+1F474 1F3FE ; fully-qualified # 👴🏾 old man: medium-dark skin tone
+1F474 1F3FF ; fully-qualified # 👴🏿 old man: dark skin tone
+1F475 ; fully-qualified # 👵 old woman
+1F475 1F3FB ; fully-qualified # 👵🏻 old woman: light skin tone
+1F475 1F3FC ; fully-qualified # 👵🏼 old woman: medium-light skin tone
+1F475 1F3FD ; fully-qualified # 👵🏽 old woman: medium skin tone
+1F475 1F3FE ; fully-qualified # 👵🏾 old woman: medium-dark skin tone
+1F475 1F3FF ; fully-qualified # 👵🏿 old woman: dark skin tone
+
+# subgroup: person-role
+1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ man health worker
+1F468 200D 2695 ; non-fully-qualified # 👨‍⚕ man health worker
+1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ man health worker: light skin tone
+1F468 1F3FB 200D 2695 ; non-fully-qualified # 👨🏻‍⚕ man health worker: light skin tone
+1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ man health worker: medium-light skin tone
+1F468 1F3FC 200D 2695 ; non-fully-qualified # 👨🏼‍⚕ man health worker: medium-light skin tone
+1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ man health worker: medium skin tone
+1F468 1F3FD 200D 2695 ; non-fully-qualified # 👨🏽‍⚕ man health worker: medium skin tone
+1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ man health worker: medium-dark skin tone
+1F468 1F3FE 200D 2695 ; non-fully-qualified # 👨🏾‍⚕ man health worker: medium-dark skin tone
+1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ man health worker: dark skin tone
+1F468 1F3FF 200D 2695 ; non-fully-qualified # 👨🏿‍⚕ man health worker: dark skin tone
+1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ woman health worker
+1F469 200D 2695 ; non-fully-qualified # 👩‍⚕ woman health worker
+1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ woman health worker: light skin tone
+1F469 1F3FB 200D 2695 ; non-fully-qualified # 👩🏻‍⚕ woman health worker: light skin tone
+1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ woman health worker: medium-light skin tone
+1F469 1F3FC 200D 2695 ; non-fully-qualified # 👩🏼‍⚕ woman health worker: medium-light skin tone
+1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ woman health worker: medium skin tone
+1F469 1F3FD 200D 2695 ; non-fully-qualified # 👩🏽‍⚕ woman health worker: medium skin tone
+1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ woman health worker: medium-dark skin tone
+1F469 1F3FE 200D 2695 ; non-fully-qualified # 👩🏾‍⚕ woman health worker: medium-dark skin tone
+1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ woman health worker: dark skin tone
+1F469 1F3FF 200D 2695 ; non-fully-qualified # 👩🏿‍⚕ woman health worker: dark skin tone
+1F468 200D 1F393 ; fully-qualified # 👨‍🎓 man student
+1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 man student: light skin tone
+1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 man student: medium-light skin tone
+1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 man student: medium skin tone
+1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 man student: medium-dark skin tone
+1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 man student: dark skin tone
+1F469 200D 1F393 ; fully-qualified # 👩‍🎓 woman student
+1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 woman student: light skin tone
+1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 woman student: medium-light skin tone
+1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 woman student: medium skin tone
+1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 woman student: medium-dark skin tone
+1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 woman student: dark skin tone
+1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 man teacher
+1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 man teacher: light skin tone
+1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 man teacher: medium-light skin tone
+1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 man teacher: medium skin tone
+1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 man teacher: medium-dark skin tone
+1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 man teacher: dark skin tone
+1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 woman teacher
+1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 woman teacher: light skin tone
+1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 woman teacher: medium-light skin tone
+1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 woman teacher: medium skin tone
+1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 woman teacher: medium-dark skin tone
+1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 woman teacher: dark skin tone
+1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ man judge
+1F468 200D 2696 ; non-fully-qualified # 👨‍⚖ man judge
+1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ man judge: light skin tone
+1F468 1F3FB 200D 2696 ; non-fully-qualified # 👨🏻‍⚖ man judge: light skin tone
+1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ man judge: medium-light skin tone
+1F468 1F3FC 200D 2696 ; non-fully-qualified # 👨🏼‍⚖ man judge: medium-light skin tone
+1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ man judge: medium skin tone
+1F468 1F3FD 200D 2696 ; non-fully-qualified # 👨🏽‍⚖ man judge: medium skin tone
+1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ man judge: medium-dark skin tone
+1F468 1F3FE 200D 2696 ; non-fully-qualified # 👨🏾‍⚖ man judge: medium-dark skin tone
+1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ man judge: dark skin tone
+1F468 1F3FF 200D 2696 ; non-fully-qualified # 👨🏿‍⚖ man judge: dark skin tone
+1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ woman judge
+1F469 200D 2696 ; non-fully-qualified # 👩‍⚖ woman judge
+1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ woman judge: light skin tone
+1F469 1F3FB 200D 2696 ; non-fully-qualified # 👩🏻‍⚖ woman judge: light skin tone
+1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ woman judge: medium-light skin tone
+1F469 1F3FC 200D 2696 ; non-fully-qualified # 👩🏼‍⚖ woman judge: medium-light skin tone
+1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ woman judge: medium skin tone
+1F469 1F3FD 200D 2696 ; non-fully-qualified # 👩🏽‍⚖ woman judge: medium skin tone
+1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ woman judge: medium-dark skin tone
+1F469 1F3FE 200D 2696 ; non-fully-qualified # 👩🏾‍⚖ woman judge: medium-dark skin tone
+1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ woman judge: dark skin tone
+1F469 1F3FF 200D 2696 ; non-fully-qualified # 👩🏿‍⚖ woman judge: dark skin tone
+1F468 200D 1F33E ; fully-qualified # 👨‍🌾 man farmer
+1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 man farmer: light skin tone
+1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 man farmer: medium-light skin tone
+1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 man farmer: medium skin tone
+1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 man farmer: medium-dark skin tone
+1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 man farmer: dark skin tone
+1F469 200D 1F33E ; fully-qualified # 👩‍🌾 woman farmer
+1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 woman farmer: light skin tone
+1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 woman farmer: medium-light skin tone
+1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 woman farmer: medium skin tone
+1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 woman farmer: medium-dark skin tone
+1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 woman farmer: dark skin tone
+1F468 200D 1F373 ; fully-qualified # 👨‍🍳 man cook
+1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 man cook: light skin tone
+1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 man cook: medium-light skin tone
+1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 man cook: medium skin tone
+1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 man cook: medium-dark skin tone
+1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 man cook: dark skin tone
+1F469 200D 1F373 ; fully-qualified # 👩‍🍳 woman cook
+1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 woman cook: light skin tone
+1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 woman cook: medium-light skin tone
+1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 woman cook: medium skin tone
+1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 woman cook: medium-dark skin tone
+1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 woman cook: dark skin tone
+1F468 200D 1F527 ; fully-qualified # 👨‍🔧 man mechanic
+1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 man mechanic: light skin tone
+1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 man mechanic: medium-light skin tone
+1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 man mechanic: medium skin tone
+1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 man mechanic: medium-dark skin tone
+1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 man mechanic: dark skin tone
+1F469 200D 1F527 ; fully-qualified # 👩‍🔧 woman mechanic
+1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 woman mechanic: light skin tone
+1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 woman mechanic: medium-light skin tone
+1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 woman mechanic: medium skin tone
+1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 woman mechanic: medium-dark skin tone
+1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 woman mechanic: dark skin tone
+1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 man factory worker
+1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 man factory worker: light skin tone
+1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 man factory worker: medium-light skin tone
+1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 man factory worker: medium skin tone
+1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 man factory worker: medium-dark skin tone
+1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 man factory worker: dark skin tone
+1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 woman factory worker
+1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 woman factory worker: light skin tone
+1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 woman factory worker: medium-light skin tone
+1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 woman factory worker: medium skin tone
+1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 woman factory worker: medium-dark skin tone
+1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 woman factory worker: dark skin tone
+1F468 200D 1F4BC ; fully-qualified # 👨‍💼 man office worker
+1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 man office worker: light skin tone
+1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 man office worker: medium-light skin tone
+1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 man office worker: medium skin tone
+1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 man office worker: medium-dark skin tone
+1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 man office worker: dark skin tone
+1F469 200D 1F4BC ; fully-qualified # 👩‍💼 woman office worker
+1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 woman office worker: light skin tone
+1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 woman office worker: medium-light skin tone
+1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 woman office worker: medium skin tone
+1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 woman office worker: medium-dark skin tone
+1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 woman office worker: dark skin tone
+1F468 200D 1F52C ; fully-qualified # 👨‍🔬 man scientist
+1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 man scientist: light skin tone
+1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 man scientist: medium-light skin tone
+1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 man scientist: medium skin tone
+1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 man scientist: medium-dark skin tone
+1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 man scientist: dark skin tone
+1F469 200D 1F52C ; fully-qualified # 👩‍🔬 woman scientist
+1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 woman scientist: light skin tone
+1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 woman scientist: medium-light skin tone
+1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 woman scientist: medium skin tone
+1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 woman scientist: medium-dark skin tone
+1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 woman scientist: dark skin tone
+1F468 200D 1F4BB ; fully-qualified # 👨‍💻 man technologist
+1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 man technologist: light skin tone
+1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 man technologist: medium-light skin tone
+1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 man technologist: medium skin tone
+1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 man technologist: medium-dark skin tone
+1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 man technologist: dark skin tone
+1F469 200D 1F4BB ; fully-qualified # 👩‍💻 woman technologist
+1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 woman technologist: light skin tone
+1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 woman technologist: medium-light skin tone
+1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 woman technologist: medium skin tone
+1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 woman technologist: medium-dark skin tone
+1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 woman technologist: dark skin tone
+1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 man singer
+1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 man singer: light skin tone
+1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 man singer: medium-light skin tone
+1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 man singer: medium skin tone
+1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 man singer: medium-dark skin tone
+1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 man singer: dark skin tone
+1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 woman singer
+1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 woman singer: light skin tone
+1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 woman singer: medium-light skin tone
+1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 woman singer: medium skin tone
+1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 woman singer: medium-dark skin tone
+1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 woman singer: dark skin tone
+1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 man artist
+1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 man artist: light skin tone
+1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 man artist: medium-light skin tone
+1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 man artist: medium skin tone
+1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 man artist: medium-dark skin tone
+1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 man artist: dark skin tone
+1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 woman artist
+1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 woman artist: light skin tone
+1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 woman artist: medium-light skin tone
+1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 woman artist: medium skin tone
+1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 woman artist: medium-dark skin tone
+1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 woman artist: dark skin tone
+1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ man pilot
+1F468 200D 2708 ; non-fully-qualified # 👨‍✈ man pilot
+1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ man pilot: light skin tone
+1F468 1F3FB 200D 2708 ; non-fully-qualified # 👨🏻‍✈ man pilot: light skin tone
+1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ man pilot: medium-light skin tone
+1F468 1F3FC 200D 2708 ; non-fully-qualified # 👨🏼‍✈ man pilot: medium-light skin tone
+1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ man pilot: medium skin tone
+1F468 1F3FD 200D 2708 ; non-fully-qualified # 👨🏽‍✈ man pilot: medium skin tone
+1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ man pilot: medium-dark skin tone
+1F468 1F3FE 200D 2708 ; non-fully-qualified # 👨🏾‍✈ man pilot: medium-dark skin tone
+1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ man pilot: dark skin tone
+1F468 1F3FF 200D 2708 ; non-fully-qualified # 👨🏿‍✈ man pilot: dark skin tone
+1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ woman pilot
+1F469 200D 2708 ; non-fully-qualified # 👩‍✈ woman pilot
+1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ woman pilot: light skin tone
+1F469 1F3FB 200D 2708 ; non-fully-qualified # 👩🏻‍✈ woman pilot: light skin tone
+1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ woman pilot: medium-light skin tone
+1F469 1F3FC 200D 2708 ; non-fully-qualified # 👩🏼‍✈ woman pilot: medium-light skin tone
+1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ woman pilot: medium skin tone
+1F469 1F3FD 200D 2708 ; non-fully-qualified # 👩🏽‍✈ woman pilot: medium skin tone
+1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ woman pilot: medium-dark skin tone
+1F469 1F3FE 200D 2708 ; non-fully-qualified # 👩🏾‍✈ woman pilot: medium-dark skin tone
+1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ woman pilot: dark skin tone
+1F469 1F3FF 200D 2708 ; non-fully-qualified # 👩🏿‍✈ woman pilot: dark skin tone
+1F468 200D 1F680 ; fully-qualified # 👨‍🚀 man astronaut
+1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 man astronaut: light skin tone
+1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 man astronaut: medium-light skin tone
+1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 man astronaut: medium skin tone
+1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 man astronaut: medium-dark skin tone
+1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 man astronaut: dark skin tone
+1F469 200D 1F680 ; fully-qualified # 👩‍🚀 woman astronaut
+1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 woman astronaut: light skin tone
+1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 woman astronaut: medium-light skin tone
+1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 woman astronaut: medium skin tone
+1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 woman astronaut: medium-dark skin tone
+1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 woman astronaut: dark skin tone
+1F468 200D 1F692 ; fully-qualified # 👨‍🚒 man firefighter
+1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 man firefighter: light skin tone
+1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 man firefighter: medium-light skin tone
+1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 man firefighter: medium skin tone
+1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 man firefighter: medium-dark skin tone
+1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 man firefighter: dark skin tone
+1F469 200D 1F692 ; fully-qualified # 👩‍🚒 woman firefighter
+1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 woman firefighter: light skin tone
+1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 woman firefighter: medium-light skin tone
+1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 woman firefighter: medium skin tone
+1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 woman firefighter: medium-dark skin tone
+1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 woman firefighter: dark skin tone
+1F46E ; fully-qualified # 👮 police officer
+1F46E 1F3FB ; fully-qualified # 👮🏻 police officer: light skin tone
+1F46E 1F3FC ; fully-qualified # 👮🏼 police officer: medium-light skin tone
+1F46E 1F3FD ; fully-qualified # 👮🏽 police officer: medium skin tone
+1F46E 1F3FE ; fully-qualified # 👮🏾 police officer: medium-dark skin tone
+1F46E 1F3FF ; fully-qualified # 👮🏿 police officer: dark skin tone
+1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ man police officer
+1F46E 200D 2642 ; non-fully-qualified # 👮‍♂ man police officer
+1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ man police officer: light skin tone
+1F46E 1F3FB 200D 2642 ; non-fully-qualified # 👮🏻‍♂ man police officer: light skin tone
+1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ man police officer: medium-light skin tone
+1F46E 1F3FC 200D 2642 ; non-fully-qualified # 👮🏼‍♂ man police officer: medium-light skin tone
+1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ man police officer: medium skin tone
+1F46E 1F3FD 200D 2642 ; non-fully-qualified # 👮🏽‍♂ man police officer: medium skin tone
+1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ man police officer: medium-dark skin tone
+1F46E 1F3FE 200D 2642 ; non-fully-qualified # 👮🏾‍♂ man police officer: medium-dark skin tone
+1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ man police officer: dark skin tone
+1F46E 1F3FF 200D 2642 ; non-fully-qualified # 👮🏿‍♂ man police officer: dark skin tone
+1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ woman police officer
+1F46E 200D 2640 ; non-fully-qualified # 👮‍♀ woman police officer
+1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ woman police officer: light skin tone
+1F46E 1F3FB 200D 2640 ; non-fully-qualified # 👮🏻‍♀ woman police officer: light skin tone
+1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ woman police officer: medium-light skin tone
+1F46E 1F3FC 200D 2640 ; non-fully-qualified # 👮🏼‍♀ woman police officer: medium-light skin tone
+1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ woman police officer: medium skin tone
+1F46E 1F3FD 200D 2640 ; non-fully-qualified # 👮🏽‍♀ woman police officer: medium skin tone
+1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ woman police officer: medium-dark skin tone
+1F46E 1F3FE 200D 2640 ; non-fully-qualified # 👮🏾‍♀ woman police officer: medium-dark skin tone
+1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ woman police officer: dark skin tone
+1F46E 1F3FF 200D 2640 ; non-fully-qualified # 👮🏿‍♀ woman police officer: dark skin tone
+1F575 FE0F ; fully-qualified # 🕵️ detective
+1F575 ; non-fully-qualified # 🕵 detective
+1F575 1F3FB ; fully-qualified # 🕵🏻 detective: light skin tone
+1F575 1F3FC ; fully-qualified # 🕵🏼 detective: medium-light skin tone
+1F575 1F3FD ; fully-qualified # 🕵🏽 detective: medium skin tone
+1F575 1F3FE ; fully-qualified # 🕵🏾 detective: medium-dark skin tone
+1F575 1F3FF ; fully-qualified # 🕵🏿 detective: dark skin tone
+1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ man detective
+1F575 200D 2642 FE0F ; non-fully-qualified # 🕵‍♂️ man detective
+1F575 FE0F 200D 2642 ; non-fully-qualified # 🕵️‍♂ man detective
+1F575 200D 2642 ; non-fully-qualified # 🕵‍♂ man detective
+1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ man detective: light skin tone
+1F575 1F3FB 200D 2642 ; non-fully-qualified # 🕵🏻‍♂ man detective: light skin tone
+1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ man detective: medium-light skin tone
+1F575 1F3FC 200D 2642 ; non-fully-qualified # 🕵🏼‍♂ man detective: medium-light skin tone
+1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ man detective: medium skin tone
+1F575 1F3FD 200D 2642 ; non-fully-qualified # 🕵🏽‍♂ man detective: medium skin tone
+1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ man detective: medium-dark skin tone
+1F575 1F3FE 200D 2642 ; non-fully-qualified # 🕵🏾‍♂ man detective: medium-dark skin tone
+1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ man detective: dark skin tone
+1F575 1F3FF 200D 2642 ; non-fully-qualified # 🕵🏿‍♂ man detective: dark skin tone
+1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ woman detective
+1F575 200D 2640 FE0F ; non-fully-qualified # 🕵‍♀️ woman detective
+1F575 FE0F 200D 2640 ; non-fully-qualified # 🕵️‍♀ woman detective
+1F575 200D 2640 ; non-fully-qualified # 🕵‍♀ woman detective
+1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ woman detective: light skin tone
+1F575 1F3FB 200D 2640 ; non-fully-qualified # 🕵🏻‍♀ woman detective: light skin tone
+1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ woman detective: medium-light skin tone
+1F575 1F3FC 200D 2640 ; non-fully-qualified # 🕵🏼‍♀ woman detective: medium-light skin tone
+1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ woman detective: medium skin tone
+1F575 1F3FD 200D 2640 ; non-fully-qualified # 🕵🏽‍♀ woman detective: medium skin tone
+1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ woman detective: medium-dark skin tone
+1F575 1F3FE 200D 2640 ; non-fully-qualified # 🕵🏾‍♀ woman detective: medium-dark skin tone
+1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ woman detective: dark skin tone
+1F575 1F3FF 200D 2640 ; non-fully-qualified # 🕵🏿‍♀ woman detective: dark skin tone
+1F482 ; fully-qualified # 💂 guard
+1F482 1F3FB ; fully-qualified # 💂🏻 guard: light skin tone
+1F482 1F3FC ; fully-qualified # 💂🏼 guard: medium-light skin tone
+1F482 1F3FD ; fully-qualified # 💂🏽 guard: medium skin tone
+1F482 1F3FE ; fully-qualified # 💂🏾 guard: medium-dark skin tone
+1F482 1F3FF ; fully-qualified # 💂🏿 guard: dark skin tone
+1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ man guard
+1F482 200D 2642 ; non-fully-qualified # 💂‍♂ man guard
+1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ man guard: light skin tone
+1F482 1F3FB 200D 2642 ; non-fully-qualified # 💂🏻‍♂ man guard: light skin tone
+1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ man guard: medium-light skin tone
+1F482 1F3FC 200D 2642 ; non-fully-qualified # 💂🏼‍♂ man guard: medium-light skin tone
+1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ man guard: medium skin tone
+1F482 1F3FD 200D 2642 ; non-fully-qualified # 💂🏽‍♂ man guard: medium skin tone
+1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ man guard: medium-dark skin tone
+1F482 1F3FE 200D 2642 ; non-fully-qualified # 💂🏾‍♂ man guard: medium-dark skin tone
+1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ man guard: dark skin tone
+1F482 1F3FF 200D 2642 ; non-fully-qualified # 💂🏿‍♂ man guard: dark skin tone
+1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ woman guard
+1F482 200D 2640 ; non-fully-qualified # 💂‍♀ woman guard
+1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ woman guard: light skin tone
+1F482 1F3FB 200D 2640 ; non-fully-qualified # 💂🏻‍♀ woman guard: light skin tone
+1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ woman guard: medium-light skin tone
+1F482 1F3FC 200D 2640 ; non-fully-qualified # 💂🏼‍♀ woman guard: medium-light skin tone
+1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ woman guard: medium skin tone
+1F482 1F3FD 200D 2640 ; non-fully-qualified # 💂🏽‍♀ woman guard: medium skin tone
+1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ woman guard: medium-dark skin tone
+1F482 1F3FE 200D 2640 ; non-fully-qualified # 💂🏾‍♀ woman guard: medium-dark skin tone
+1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ woman guard: dark skin tone
+1F482 1F3FF 200D 2640 ; non-fully-qualified # 💂🏿‍♀ woman guard: dark skin tone
+1F477 ; fully-qualified # 👷 construction worker
+1F477 1F3FB ; fully-qualified # 👷🏻 construction worker: light skin tone
+1F477 1F3FC ; fully-qualified # 👷🏼 construction worker: medium-light skin tone
+1F477 1F3FD ; fully-qualified # 👷🏽 construction worker: medium skin tone
+1F477 1F3FE ; fully-qualified # 👷🏾 construction worker: medium-dark skin tone
+1F477 1F3FF ; fully-qualified # 👷🏿 construction worker: dark skin tone
+1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ man construction worker
+1F477 200D 2642 ; non-fully-qualified # 👷‍♂ man construction worker
+1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ man construction worker: light skin tone
+1F477 1F3FB 200D 2642 ; non-fully-qualified # 👷🏻‍♂ man construction worker: light skin tone
+1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ man construction worker: medium-light skin tone
+1F477 1F3FC 200D 2642 ; non-fully-qualified # 👷🏼‍♂ man construction worker: medium-light skin tone
+1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ man construction worker: medium skin tone
+1F477 1F3FD 200D 2642 ; non-fully-qualified # 👷🏽‍♂ man construction worker: medium skin tone
+1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ man construction worker: medium-dark skin tone
+1F477 1F3FE 200D 2642 ; non-fully-qualified # 👷🏾‍♂ man construction worker: medium-dark skin tone
+1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ man construction worker: dark skin tone
+1F477 1F3FF 200D 2642 ; non-fully-qualified # 👷🏿‍♂ man construction worker: dark skin tone
+1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ woman construction worker
+1F477 200D 2640 ; non-fully-qualified # 👷‍♀ woman construction worker
+1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ woman construction worker: light skin tone
+1F477 1F3FB 200D 2640 ; non-fully-qualified # 👷🏻‍♀ woman construction worker: light skin tone
+1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ woman construction worker: medium-light skin tone
+1F477 1F3FC 200D 2640 ; non-fully-qualified # 👷🏼‍♀ woman construction worker: medium-light skin tone
+1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ woman construction worker: medium skin tone
+1F477 1F3FD 200D 2640 ; non-fully-qualified # 👷🏽‍♀ woman construction worker: medium skin tone
+1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ woman construction worker: medium-dark skin tone
+1F477 1F3FE 200D 2640 ; non-fully-qualified # 👷🏾‍♀ woman construction worker: medium-dark skin tone
+1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ woman construction worker: dark skin tone
+1F477 1F3FF 200D 2640 ; non-fully-qualified # 👷🏿‍♀ woman construction worker: dark skin tone
+1F934 ; fully-qualified # 🤴 prince
+1F934 1F3FB ; fully-qualified # 🤴🏻 prince: light skin tone
+1F934 1F3FC ; fully-qualified # 🤴🏼 prince: medium-light skin tone
+1F934 1F3FD ; fully-qualified # 🤴🏽 prince: medium skin tone
+1F934 1F3FE ; fully-qualified # 🤴🏾 prince: medium-dark skin tone
+1F934 1F3FF ; fully-qualified # 🤴🏿 prince: dark skin tone
+1F478 ; fully-qualified # 👸 princess
+1F478 1F3FB ; fully-qualified # 👸🏻 princess: light skin tone
+1F478 1F3FC ; fully-qualified # 👸🏼 princess: medium-light skin tone
+1F478 1F3FD ; fully-qualified # 👸🏽 princess: medium skin tone
+1F478 1F3FE ; fully-qualified # 👸🏾 princess: medium-dark skin tone
+1F478 1F3FF ; fully-qualified # 👸🏿 princess: dark skin tone
+1F473 ; fully-qualified # 👳 person wearing turban
+1F473 1F3FB ; fully-qualified # 👳🏻 person wearing turban: light skin tone
+1F473 1F3FC ; fully-qualified # 👳🏼 person wearing turban: medium-light skin tone
+1F473 1F3FD ; fully-qualified # 👳🏽 person wearing turban: medium skin tone
+1F473 1F3FE ; fully-qualified # 👳🏾 person wearing turban: medium-dark skin tone
+1F473 1F3FF ; fully-qualified # 👳🏿 person wearing turban: dark skin tone
+1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ man wearing turban
+1F473 200D 2642 ; non-fully-qualified # 👳‍♂ man wearing turban
+1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ man wearing turban: light skin tone
+1F473 1F3FB 200D 2642 ; non-fully-qualified # 👳🏻‍♂ man wearing turban: light skin tone
+1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ man wearing turban: medium-light skin tone
+1F473 1F3FC 200D 2642 ; non-fully-qualified # 👳🏼‍♂ man wearing turban: medium-light skin tone
+1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ man wearing turban: medium skin tone
+1F473 1F3FD 200D 2642 ; non-fully-qualified # 👳🏽‍♂ man wearing turban: medium skin tone
+1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ man wearing turban: medium-dark skin tone
+1F473 1F3FE 200D 2642 ; non-fully-qualified # 👳🏾‍♂ man wearing turban: medium-dark skin tone
+1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ man wearing turban: dark skin tone
+1F473 1F3FF 200D 2642 ; non-fully-qualified # 👳🏿‍♂ man wearing turban: dark skin tone
+1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ woman wearing turban
+1F473 200D 2640 ; non-fully-qualified # 👳‍♀ woman wearing turban
+1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ woman wearing turban: light skin tone
+1F473 1F3FB 200D 2640 ; non-fully-qualified # 👳🏻‍♀ woman wearing turban: light skin tone
+1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ woman wearing turban: medium-light skin tone
+1F473 1F3FC 200D 2640 ; non-fully-qualified # 👳🏼‍♀ woman wearing turban: medium-light skin tone
+1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ woman wearing turban: medium skin tone
+1F473 1F3FD 200D 2640 ; non-fully-qualified # 👳🏽‍♀ woman wearing turban: medium skin tone
+1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ woman wearing turban: medium-dark skin tone
+1F473 1F3FE 200D 2640 ; non-fully-qualified # 👳🏾‍♀ woman wearing turban: medium-dark skin tone
+1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ woman wearing turban: dark skin tone
+1F473 1F3FF 200D 2640 ; non-fully-qualified # 👳🏿‍♀ woman wearing turban: dark skin tone
+1F472 ; fully-qualified # 👲 man with Chinese cap
+1F472 1F3FB ; fully-qualified # 👲🏻 man with Chinese cap: light skin tone
+1F472 1F3FC ; fully-qualified # 👲🏼 man with Chinese cap: medium-light skin tone
+1F472 1F3FD ; fully-qualified # 👲🏽 man with Chinese cap: medium skin tone
+1F472 1F3FE ; fully-qualified # 👲🏾 man with Chinese cap: medium-dark skin tone
+1F472 1F3FF ; fully-qualified # 👲🏿 man with Chinese cap: dark skin tone
+1F9D5 ; fully-qualified # 🧕 woman with headscarf
+1F9D5 1F3FB ; fully-qualified # 🧕🏻 woman with headscarf: light skin tone
+1F9D5 1F3FC ; fully-qualified # 🧕🏼 woman with headscarf: medium-light skin tone
+1F9D5 1F3FD ; fully-qualified # 🧕🏽 woman with headscarf: medium skin tone
+1F9D5 1F3FE ; fully-qualified # 🧕🏾 woman with headscarf: medium-dark skin tone
+1F9D5 1F3FF ; fully-qualified # 🧕🏿 woman with headscarf: dark skin tone
+1F9D4 ; fully-qualified # 🧔 bearded person
+1F9D4 1F3FB ; fully-qualified # 🧔🏻 bearded person: light skin tone
+1F9D4 1F3FC ; fully-qualified # 🧔🏼 bearded person: medium-light skin tone
+1F9D4 1F3FD ; fully-qualified # 🧔🏽 bearded person: medium skin tone
+1F9D4 1F3FE ; fully-qualified # 🧔🏾 bearded person: medium-dark skin tone
+1F9D4 1F3FF ; fully-qualified # 🧔🏿 bearded person: dark skin tone
+1F471 ; fully-qualified # 👱 blond-haired person
+1F471 1F3FB ; fully-qualified # 👱🏻 blond-haired person: light skin tone
+1F471 1F3FC ; fully-qualified # 👱🏼 blond-haired person: medium-light skin tone
+1F471 1F3FD ; fully-qualified # 👱🏽 blond-haired person: medium skin tone
+1F471 1F3FE ; fully-qualified # 👱🏾 blond-haired person: medium-dark skin tone
+1F471 1F3FF ; fully-qualified # 👱🏿 blond-haired person: dark skin tone
+1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ blond-haired man
+1F471 200D 2642 ; non-fully-qualified # 👱‍♂ blond-haired man
+1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ blond-haired man: light skin tone
+1F471 1F3FB 200D 2642 ; non-fully-qualified # 👱🏻‍♂ blond-haired man: light skin tone
+1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ blond-haired man: medium-light skin tone
+1F471 1F3FC 200D 2642 ; non-fully-qualified # 👱🏼‍♂ blond-haired man: medium-light skin tone
+1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ blond-haired man: medium skin tone
+1F471 1F3FD 200D 2642 ; non-fully-qualified # 👱🏽‍♂ blond-haired man: medium skin tone
+1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ blond-haired man: medium-dark skin tone
+1F471 1F3FE 200D 2642 ; non-fully-qualified # 👱🏾‍♂ blond-haired man: medium-dark skin tone
+1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ blond-haired man: dark skin tone
+1F471 1F3FF 200D 2642 ; non-fully-qualified # 👱🏿‍♂ blond-haired man: dark skin tone
+1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ blond-haired woman
+1F471 200D 2640 ; non-fully-qualified # 👱‍♀ blond-haired woman
+1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ blond-haired woman: light skin tone
+1F471 1F3FB 200D 2640 ; non-fully-qualified # 👱🏻‍♀ blond-haired woman: light skin tone
+1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ blond-haired woman: medium-light skin tone
+1F471 1F3FC 200D 2640 ; non-fully-qualified # 👱🏼‍♀ blond-haired woman: medium-light skin tone
+1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ blond-haired woman: medium skin tone
+1F471 1F3FD 200D 2640 ; non-fully-qualified # 👱🏽‍♀ blond-haired woman: medium skin tone
+1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ blond-haired woman: medium-dark skin tone
+1F471 1F3FE 200D 2640 ; non-fully-qualified # 👱🏾‍♀ blond-haired woman: medium-dark skin tone
+1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ blond-haired woman: dark skin tone
+1F471 1F3FF 200D 2640 ; non-fully-qualified # 👱🏿‍♀ blond-haired woman: dark skin tone
+1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 man, red haired
+1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 man, red haired: light skin tone
+1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 man, red haired: medium-light skin tone
+1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 man, red haired: medium skin tone
+1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 man, red haired: medium-dark skin tone
+1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 man, red haired: dark skin tone
+1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 woman, red haired
+1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 woman, red haired: light skin tone
+1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 woman, red haired: medium-light skin tone
+1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 woman, red haired: medium skin tone
+1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 woman, red haired: medium-dark skin tone
+1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 woman, red haired: dark skin tone
+1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 man, curly haired
+1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 man, curly haired: light skin tone
+1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 man, curly haired: medium-light skin tone
+1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 man, curly haired: medium skin tone
+1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 man, curly haired: medium-dark skin tone
+1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 man, curly haired: dark skin tone
+1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 woman, curly haired
+1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 woman, curly haired: light skin tone
+1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 woman, curly haired: medium-light skin tone
+1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 woman, curly haired: medium skin tone
+1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 woman, curly haired: medium-dark skin tone
+1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 woman, curly haired: dark skin tone
+1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 man, bald
+1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 man, bald: light skin tone
+1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 man, bald: medium-light skin tone
+1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 man, bald: medium skin tone
+1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 man, bald: medium-dark skin tone
+1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 man, bald: dark skin tone
+1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 woman, bald
+1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 woman, bald: light skin tone
+1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 woman, bald: medium-light skin tone
+1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 woman, bald: medium skin tone
+1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 woman, bald: medium-dark skin tone
+1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 woman, bald: dark skin tone
+1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 man, white haired
+1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 man, white haired: light skin tone
+1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 man, white haired: medium-light skin tone
+1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 man, white haired: medium skin tone
+1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 man, white haired: medium-dark skin tone
+1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 man, white haired: dark skin tone
+1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 woman, white haired
+1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 woman, white haired: light skin tone
+1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 woman, white haired: medium-light skin tone
+1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 woman, white haired: medium skin tone
+1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 woman, white haired: medium-dark skin tone
+1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 woman, white haired: dark skin tone
+1F935 ; fully-qualified # 🤵 man in tuxedo
+1F935 1F3FB ; fully-qualified # 🤵🏻 man in tuxedo: light skin tone
+1F935 1F3FC ; fully-qualified # 🤵🏼 man in tuxedo: medium-light skin tone
+1F935 1F3FD ; fully-qualified # 🤵🏽 man in tuxedo: medium skin tone
+1F935 1F3FE ; fully-qualified # 🤵🏾 man in tuxedo: medium-dark skin tone
+1F935 1F3FF ; fully-qualified # 🤵🏿 man in tuxedo: dark skin tone
+1F470 ; fully-qualified # 👰 bride with veil
+1F470 1F3FB ; fully-qualified # 👰🏻 bride with veil: light skin tone
+1F470 1F3FC ; fully-qualified # 👰🏼 bride with veil: medium-light skin tone
+1F470 1F3FD ; fully-qualified # 👰🏽 bride with veil: medium skin tone
+1F470 1F3FE ; fully-qualified # 👰🏾 bride with veil: medium-dark skin tone
+1F470 1F3FF ; fully-qualified # 👰🏿 bride with veil: dark skin tone
+1F930 ; fully-qualified # 🤰 pregnant woman
+1F930 1F3FB ; fully-qualified # 🤰🏻 pregnant woman: light skin tone
+1F930 1F3FC ; fully-qualified # 🤰🏼 pregnant woman: medium-light skin tone
+1F930 1F3FD ; fully-qualified # 🤰🏽 pregnant woman: medium skin tone
+1F930 1F3FE ; fully-qualified # 🤰🏾 pregnant woman: medium-dark skin tone
+1F930 1F3FF ; fully-qualified # 🤰🏿 pregnant woman: dark skin tone
+1F931 ; fully-qualified # 🤱 breast-feeding
+1F931 1F3FB ; fully-qualified # 🤱🏻 breast-feeding: light skin tone
+1F931 1F3FC ; fully-qualified # 🤱🏼 breast-feeding: medium-light skin tone
+1F931 1F3FD ; fully-qualified # 🤱🏽 breast-feeding: medium skin tone
+1F931 1F3FE ; fully-qualified # 🤱🏾 breast-feeding: medium-dark skin tone
+1F931 1F3FF ; fully-qualified # 🤱🏿 breast-feeding: dark skin tone
+
+# subgroup: person-fantasy
+1F47C ; fully-qualified # 👼 baby angel
+1F47C 1F3FB ; fully-qualified # 👼🏻 baby angel: light skin tone
+1F47C 1F3FC ; fully-qualified # 👼🏼 baby angel: medium-light skin tone
+1F47C 1F3FD ; fully-qualified # 👼🏽 baby angel: medium skin tone
+1F47C 1F3FE ; fully-qualified # 👼🏾 baby angel: medium-dark skin tone
+1F47C 1F3FF ; fully-qualified # 👼🏿 baby angel: dark skin tone
+1F385 ; fully-qualified # 🎅 Santa Claus
+1F385 1F3FB ; fully-qualified # 🎅🏻 Santa Claus: light skin tone
+1F385 1F3FC ; fully-qualified # 🎅🏼 Santa Claus: medium-light skin tone
+1F385 1F3FD ; fully-qualified # 🎅🏽 Santa Claus: medium skin tone
+1F385 1F3FE ; fully-qualified # 🎅🏾 Santa Claus: medium-dark skin tone
+1F385 1F3FF ; fully-qualified # 🎅🏿 Santa Claus: dark skin tone
+1F936 ; fully-qualified # 🤶 Mrs. Claus
+1F936 1F3FB ; fully-qualified # 🤶🏻 Mrs. Claus: light skin tone
+1F936 1F3FC ; fully-qualified # 🤶🏼 Mrs. Claus: medium-light skin tone
+1F936 1F3FD ; fully-qualified # 🤶🏽 Mrs. Claus: medium skin tone
+1F936 1F3FE ; fully-qualified # 🤶🏾 Mrs. Claus: medium-dark skin tone
+1F936 1F3FF ; fully-qualified # 🤶🏿 Mrs. Claus: dark skin tone
+1F9B8 ; fully-qualified # 🦸 superhero
+1F9B8 1F3FB ; fully-qualified # 🦸🏻 superhero: light skin tone
+1F9B8 1F3FC ; fully-qualified # 🦸🏼 superhero: medium-light skin tone
+1F9B8 1F3FD ; fully-qualified # 🦸🏽 superhero: medium skin tone
+1F9B8 1F3FE ; fully-qualified # 🦸🏾 superhero: medium-dark skin tone
+1F9B8 1F3FF ; fully-qualified # 🦸🏿 superhero: dark skin tone
+1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ woman superhero
+1F9B8 200D 2640 ; non-fully-qualified # 🦸‍♀ woman superhero
+1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ woman superhero: light skin tone
+1F9B8 1F3FB 200D 2640 ; non-fully-qualified # 🦸🏻‍♀ woman superhero: light skin tone
+1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ woman superhero: medium-light skin tone
+1F9B8 1F3FC 200D 2640 ; non-fully-qualified # 🦸🏼‍♀ woman superhero: medium-light skin tone
+1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ woman superhero: medium skin tone
+1F9B8 1F3FD 200D 2640 ; non-fully-qualified # 🦸🏽‍♀ woman superhero: medium skin tone
+1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ woman superhero: medium-dark skin tone
+1F9B8 1F3FE 200D 2640 ; non-fully-qualified # 🦸🏾‍♀ woman superhero: medium-dark skin tone
+1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ woman superhero: dark skin tone
+1F9B8 1F3FF 200D 2640 ; non-fully-qualified # 🦸🏿‍♀ woman superhero: dark skin tone
+1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ man superhero
+1F9B8 200D 2642 ; non-fully-qualified # 🦸‍♂ man superhero
+1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ man superhero: light skin tone
+1F9B8 1F3FB 200D 2642 ; non-fully-qualified # 🦸🏻‍♂ man superhero: light skin tone
+1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ man superhero: medium-light skin tone
+1F9B8 1F3FC 200D 2642 ; non-fully-qualified # 🦸🏼‍♂ man superhero: medium-light skin tone
+1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ man superhero: medium skin tone
+1F9B8 1F3FD 200D 2642 ; non-fully-qualified # 🦸🏽‍♂ man superhero: medium skin tone
+1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ man superhero: medium-dark skin tone
+1F9B8 1F3FE 200D 2642 ; non-fully-qualified # 🦸🏾‍♂ man superhero: medium-dark skin tone
+1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ man superhero: dark skin tone
+1F9B8 1F3FF 200D 2642 ; non-fully-qualified # 🦸🏿‍♂ man superhero: dark skin tone
+1F9B9 ; fully-qualified # 🦹 supervillain
+1F9B9 1F3FB ; fully-qualified # 🦹🏻 supervillain: light skin tone
+1F9B9 1F3FC ; fully-qualified # 🦹🏼 supervillain: medium-light skin tone
+1F9B9 1F3FD ; fully-qualified # 🦹🏽 supervillain: medium skin tone
+1F9B9 1F3FE ; fully-qualified # 🦹🏾 supervillain: medium-dark skin tone
+1F9B9 1F3FF ; fully-qualified # 🦹🏿 supervillain: dark skin tone
+1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ woman supervillain
+1F9B9 200D 2640 ; non-fully-qualified # 🦹‍♀ woman supervillain
+1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ woman supervillain: light skin tone
+1F9B9 1F3FB 200D 2640 ; non-fully-qualified # 🦹🏻‍♀ woman supervillain: light skin tone
+1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ woman supervillain: medium-light skin tone
+1F9B9 1F3FC 200D 2640 ; non-fully-qualified # 🦹🏼‍♀ woman supervillain: medium-light skin tone
+1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ woman supervillain: medium skin tone
+1F9B9 1F3FD 200D 2640 ; non-fully-qualified # 🦹🏽‍♀ woman supervillain: medium skin tone
+1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ woman supervillain: medium-dark skin tone
+1F9B9 1F3FE 200D 2640 ; non-fully-qualified # 🦹🏾‍♀ woman supervillain: medium-dark skin tone
+1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ woman supervillain: dark skin tone
+1F9B9 1F3FF 200D 2640 ; non-fully-qualified # 🦹🏿‍♀ woman supervillain: dark skin tone
+1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ man supervillain
+1F9B9 200D 2642 ; non-fully-qualified # 🦹‍♂ man supervillain
+1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ man supervillain: light skin tone
+1F9B9 1F3FB 200D 2642 ; non-fully-qualified # 🦹🏻‍♂ man supervillain: light skin tone
+1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ man supervillain: medium-light skin tone
+1F9B9 1F3FC 200D 2642 ; non-fully-qualified # 🦹🏼‍♂ man supervillain: medium-light skin tone
+1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ man supervillain: medium skin tone
+1F9B9 1F3FD 200D 2642 ; non-fully-qualified # 🦹🏽‍♂ man supervillain: medium skin tone
+1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ man supervillain: medium-dark skin tone
+1F9B9 1F3FE 200D 2642 ; non-fully-qualified # 🦹🏾‍♂ man supervillain: medium-dark skin tone
+1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ man supervillain: dark skin tone
+1F9B9 1F3FF 200D 2642 ; non-fully-qualified # 🦹🏿‍♂ man supervillain: dark skin tone
+1F9D9 ; fully-qualified # 🧙 mage
+1F9D9 1F3FB ; fully-qualified # 🧙🏻 mage: light skin tone
+1F9D9 1F3FC ; fully-qualified # 🧙🏼 mage: medium-light skin tone
+1F9D9 1F3FD ; fully-qualified # 🧙🏽 mage: medium skin tone
+1F9D9 1F3FE ; fully-qualified # 🧙🏾 mage: medium-dark skin tone
+1F9D9 1F3FF ; fully-qualified # 🧙🏿 mage: dark skin tone
+1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ woman mage
+1F9D9 200D 2640 ; non-fully-qualified # 🧙‍♀ woman mage
+1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ woman mage: light skin tone
+1F9D9 1F3FB 200D 2640 ; non-fully-qualified # 🧙🏻‍♀ woman mage: light skin tone
+1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ woman mage: medium-light skin tone