diff options
Diffstat (limited to 'src/gui/text/qfontengine.cpp')
-rw-r--r-- | src/gui/text/qfontengine.cpp | 326 |
1 files changed, 199 insertions, 127 deletions
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 72f90ae53b..4e78aaac2e 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <qdebug.h> #include <private/qfontengine_p.h> @@ -49,6 +13,7 @@ #include "qpainter.h" #include "qpainterpath.h" #include "qvarlengtharray.h" +#include "qtextengine_p.h" #include <qmath.h> #include <qendian.h> #include <private/qstringiterator_p.h> @@ -90,17 +55,6 @@ static inline bool qSafeFromBigEndian(const uchar *source, const uchar *end, T * return true; } -// Harfbuzz helper functions - -#if QT_CONFIG(harfbuzz) -Q_GLOBAL_STATIC_WITH_ARGS(bool, useHarfbuzzNG,(qgetenv("QT_HARFBUZZ") != "old")) - -bool qt_useHarfbuzzNG() -{ - return *useHarfbuzzNG(); -} -#endif - int QFontEngine::getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints) { Q_UNUSED(glyph); @@ -199,20 +153,20 @@ void *QFontEngine::harfbuzzFont() const { Q_ASSERT(type() != QFontEngine::Multi); #if QT_CONFIG(harfbuzz) - if (qt_useHarfbuzzNG()) - return hb_qt_font_get_for_engine(const_cast<QFontEngine *>(this)); -#endif + return hb_qt_font_get_for_engine(const_cast<QFontEngine *>(this)); +#else return nullptr; +#endif } void *QFontEngine::harfbuzzFace() const { Q_ASSERT(type() != QFontEngine::Multi); #if QT_CONFIG(harfbuzz) - if (qt_useHarfbuzzNG()) - return hb_qt_face_get_for_engine(const_cast<QFontEngine *>(this)); -#endif + return hb_qt_face_get_for_engine(const_cast<QFontEngine *>(this)); +#else return nullptr; +#endif } bool QFontEngine::supportsScript(QChar::Script script) const @@ -227,23 +181,23 @@ bool QFontEngine::supportsScript(QChar::Script script) const return true; #if QT_CONFIG(harfbuzz) - if (qt_useHarfbuzzNG()) { - // in AAT fonts, 'gsub' table is effectively replaced by 'mort'/'morx' table - uint lenMort = 0, lenMorx = 0; - if (getSfntTableData(MAKE_TAG('m','o','r','t'), nullptr, &lenMort) || getSfntTableData(MAKE_TAG('m','o','r','x'), nullptr, &lenMorx)) - return true; + // in AAT fonts, 'gsub' table is effectively replaced by 'mort'/'morx' table + uint lenMort = 0, lenMorx = 0; + if (getSfntTableData(QFont::Tag("mort").value(), nullptr, &lenMort) + || getSfntTableData(QFont::Tag("morx").value(), nullptr, &lenMorx)) { + return true; + } - if (hb_face_t *face = hb_qt_face_get_for_engine(const_cast<QFontEngine *>(this))) { - unsigned int script_count = HB_OT_MAX_TAGS_PER_SCRIPT; - hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT]; + if (hb_face_t *face = hb_qt_face_get_for_engine(const_cast<QFontEngine *>(this))) { + unsigned int script_count = HB_OT_MAX_TAGS_PER_SCRIPT; + hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT]; - hb_ot_tags_from_script_and_language(hb_qt_script_to_script(script), HB_LANGUAGE_INVALID, - &script_count, script_tags, - nullptr, nullptr); + hb_ot_tags_from_script_and_language(hb_qt_script_to_script(script), HB_LANGUAGE_INVALID, + &script_count, script_tags, + nullptr, nullptr); - if (hb_ot_layout_table_select_script(face, HB_OT_TAG_GSUB, script_count, script_tags, nullptr, nullptr)) - return true; - } + if (hb_ot_layout_table_select_script(face, HB_OT_TAG_GSUB, script_count, script_tags, nullptr, nullptr)) + return true; } #endif return false; @@ -430,11 +384,16 @@ void QFontEngine::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rig bool QFontEngine::processHheaTable() const { - QByteArray hhea = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a')); + QByteArray hhea = getSfntTable(QFont::Tag("hhea").value()); if (hhea.size() >= 10) { - qint16 ascent = qFromBigEndian<qint16>(hhea.constData() + 4); - qint16 descent = qFromBigEndian<qint16>(hhea.constData() + 6); - qint16 leading = qFromBigEndian<qint16>(hhea.constData() + 8); + auto ptr = hhea.constData(); + qint16 ascent = qFromBigEndian<qint16>(ptr + 4); + qint16 descent = qFromBigEndian<qint16>(ptr + 6); + qint16 leading = qFromBigEndian<qint16>(ptr + 8); + + // Some fonts may have invalid HHEA data. We detect this and bail out. + if (ascent == 0 && descent == 0) + return false; QFixed unitsPerEm = emSquareSize(); m_ascent = QFixed::fromReal(ascent * fontDef.pixelSize) / unitsPerEm; @@ -450,13 +409,22 @@ bool QFontEngine::processHheaTable() const void QFontEngine::initializeHeightMetrics() const { - bool hasEmbeddedBitmaps = !getSfntTable(MAKE_TAG('E', 'B', 'L', 'C')).isEmpty() || !getSfntTable(MAKE_TAG('C', 'B', 'L', 'C')).isEmpty(); + bool hasEmbeddedBitmaps = + !getSfntTable(QFont::Tag("EBLC").value()).isEmpty() + || !getSfntTable(QFont::Tag("CBLC").value()).isEmpty() + || !getSfntTable(QFont::Tag("bdat").value()).isEmpty(); if (!hasEmbeddedBitmaps) { // Get HHEA table values if available processHheaTable(); // Allow OS/2 metrics to override if present processOS2Table(); + + if (!supportsSubPixelPositions()) { + m_ascent = m_ascent.round(); + m_descent = m_descent.round(); + m_leading = m_leading.round(); + } } m_heightMetricsQueried = true; @@ -464,24 +432,32 @@ void QFontEngine::initializeHeightMetrics() const bool QFontEngine::processOS2Table() const { - QByteArray os2 = getSfntTable(MAKE_TAG('O', 'S', '/', '2')); + QByteArray os2 = getSfntTable(QFont::Tag("OS/2").value()); if (os2.size() >= 78) { - quint16 fsSelection = qFromBigEndian<quint16>(os2.constData() + 62); - qint16 typoAscent = qFromBigEndian<qint16>(os2.constData() + 68); - qint16 typoDescent = qFromBigEndian<qint16>(os2.constData() + 70); - qint16 typoLineGap = qFromBigEndian<qint16>(os2.constData() + 72); - quint16 winAscent = qFromBigEndian<quint16>(os2.constData() + 74); - quint16 winDescent = qFromBigEndian<quint16>(os2.constData() + 76); + auto ptr = os2.constData(); + quint16 fsSelection = qFromBigEndian<quint16>(ptr + 62); + qint16 typoAscent = qFromBigEndian<qint16>(ptr + 68); + qint16 typoDescent = qFromBigEndian<qint16>(ptr + 70); + qint16 typoLineGap = qFromBigEndian<qint16>(ptr + 72); + quint16 winAscent = qFromBigEndian<quint16>(ptr + 74); + quint16 winDescent = qFromBigEndian<quint16>(ptr + 76); enum { USE_TYPO_METRICS = 0x80 }; QFixed unitsPerEm = emSquareSize(); if (fsSelection & USE_TYPO_METRICS) { + // Some fonts may have invalid OS/2 data. We detect this and bail out. + if (typoAscent == 0 && typoDescent == 0) + return false; m_ascent = QFixed::fromReal(typoAscent * fontDef.pixelSize) / unitsPerEm; m_descent = -QFixed::fromReal(typoDescent * fontDef.pixelSize) / unitsPerEm; m_leading = QFixed::fromReal(typoLineGap * fontDef.pixelSize) / unitsPerEm; } else { + // Some fonts may have invalid OS/2 data. We detect this and bail out. + if (winAscent == 0 && winDescent == 0) + return false; m_ascent = QFixed::fromReal(winAscent * fontDef.pixelSize) / unitsPerEm; m_descent = QFixed::fromReal(winDescent * fontDef.pixelSize) / unitsPerEm; + m_leading = QFixed{}; } return true; @@ -532,7 +508,7 @@ qreal QFontEngine::minRightBearing() const if (m_minRightBearing == kBearingNotInitialized) { // Try the 'hhea' font table first, which covers the entire font - QByteArray hheaTable = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a')); + QByteArray hheaTable = getSfntTable(QFont::Tag("hhea").value()); if (hheaTable.size() >= int(kMinRightSideBearingOffset + sizeof(qint16))) { const uchar *tableData = reinterpret_cast<const uchar *>(hheaTable.constData()); Q_ASSERT(q16Dot16ToFloat(qFromBigEndian<quint32>(tableData)) == 1.0); @@ -596,6 +572,16 @@ qreal QFontEngine::minRightBearing() const return m_minRightBearing; } +glyph_metrics_t QFontEngine::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + const QFixed leftBearing = firstLeftBearing(glyphs); + const QFixed rightBearing = lastRightBearing(glyphs); + return glyph_metrics_t(leftBearing, -(ascent()), w - leftBearing - rightBearing, ascent() + descent(), w, 0); +} + glyph_metrics_t QFontEngine::tightBoundingBox(const QGlyphLayout &glyphs) { glyph_metrics_t overall; @@ -611,9 +597,9 @@ glyph_metrics_t QFontEngine::tightBoundingBox(const QGlyphLayout &glyphs) QFixed y = overall.yoff + glyphs.offsets[i].y + bb.y; overall.x = qMin(overall.x, x); overall.y = qMin(overall.y, y); - xmax = qMax(xmax, x + bb.width); - ymax = qMax(ymax, y + bb.height); - overall.xoff += bb.xoff; + xmax = qMax(xmax, x.ceil() + bb.width); + ymax = qMax(ymax, y.ceil() + bb.height); + overall.xoff += glyphs.effectiveAdvance(i); overall.yoff += bb.yoff; } overall.height = qMax(overall.height, ymax - overall.y); @@ -1062,7 +1048,7 @@ void QFontEngine::loadKerningPairs(QFixed scalingFactor) { kerning_pairs.clear(); - QByteArray tab = getSfntTable(MAKE_TAG('k', 'e', 'r', 'n')); + QByteArray tab = getSfntTable(QFont::Tag("kern").value()); if (tab.isEmpty()) return; @@ -1151,7 +1137,7 @@ end: int QFontEngine::glyphCount() const { - QByteArray maxpTable = getSfntTable(MAKE_TAG('m', 'a', 'x', 'p')); + QByteArray maxpTable = getSfntTable(QFont::Tag("maxp").value()); if (maxpTable.size() < 6) return 0; @@ -1198,7 +1184,7 @@ const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSy int tableToUse = -1; int score = Invalid; for (int n = 0; n < numTables; ++n) { - quint16 platformId; + quint16 platformId = 0; if (!qSafeFromBigEndian(maps + 8 * n, endPtr, &platformId)) return nullptr; @@ -1249,6 +1235,7 @@ const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSy default: break; } + break; default: break; } @@ -1328,7 +1315,7 @@ resolveTable: quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint unicode) { const uchar *end = cmap + cmapSize; - quint16 format; + quint16 format = 0; if (!qSafeFromBigEndian(cmap, end, &format)) return 0; @@ -1345,7 +1332,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint if (unicode >= 0xffff) return 0; - quint16 segCountX2; + quint16 segCountX2 = 0; if (!qSafeFromBigEndian(cmap + 6, end, &segCountX2)) return 0; @@ -1353,7 +1340,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint int i = 0; for (; i < segCountX2/2; ++i) { - quint16 codePoint; + quint16 codePoint = 0; if (!qSafeFromBigEndian(ends + 2 * i, end, &codePoint)) return 0; if (codePoint >= unicode) @@ -1362,7 +1349,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint const unsigned char *idx = ends + segCountX2 + 2 + 2*i; - quint16 startIndex; + quint16 startIndex = 0; if (!qSafeFromBigEndian(idx, end, &startIndex)) return 0; if (startIndex > unicode) @@ -1370,20 +1357,20 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint idx += segCountX2; - quint16 tmp; + quint16 tmp = 0; if (!qSafeFromBigEndian(idx, end, &tmp)) return 0; qint16 idDelta = qint16(tmp); idx += segCountX2; - quint16 idRangeoffset_t; + quint16 idRangeoffset_t = 0; if (!qSafeFromBigEndian(idx, end, &idRangeoffset_t)) return 0; - quint16 glyphIndex; + quint16 glyphIndex = 0; if (idRangeoffset_t) { - quint16 id; + quint16 id = 0; if (!qSafeFromBigEndian(idRangeoffset_t + 2 * (unicode - startIndex) + idx, end, &id)) return 0; @@ -1396,17 +1383,17 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint } return glyphIndex; } else if (format == 6) { - quint16 tableSize; + quint16 tableSize = 0; if (!qSafeFromBigEndian(cmap + 2, end, &tableSize)) return 0; - quint16 firstCode6; + quint16 firstCode6 = 0; if (!qSafeFromBigEndian(cmap + 6, end, &firstCode6)) return 0; if (unicode < firstCode6) return 0; - quint16 entryCount6; + quint16 entryCount6 = 0; if (!qSafeFromBigEndian(cmap + 8, end, &entryCount6)) return 0; if (entryCount6 * 2 + 10 > tableSize) @@ -1422,7 +1409,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint qSafeFromBigEndian(cmap + 10 + (entryIndex6 * 2), end, &index); return index; } else if (format == 12) { - quint32 nGroups; + quint32 nGroups = 0; if (!qSafeFromBigEndian(cmap + 12, end, &nGroups)) return 0; @@ -1432,19 +1419,19 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint while (left <= right) { int middle = left + ( ( right - left ) >> 1 ); - quint32 startCharCode; + quint32 startCharCode = 0; if (!qSafeFromBigEndian(cmap + 12 * middle, end, &startCharCode)) return 0; if (unicode < startCharCode) right = middle - 1; else { - quint32 endCharCode; + quint32 endCharCode = 0; if (!qSafeFromBigEndian(cmap + 12 * middle + 4, end, &endCharCode)) return 0; if (unicode <= endCharCode) { - quint32 index; + quint32 index = 0; if (!qSafeFromBigEndian(cmap + 12 * middle + 8, end, &index)) return 0; @@ -1486,6 +1473,17 @@ bool QFontEngine::hasUnreliableGlyphOutline() const return glyphFormat == QFontEngine::Format_ARGB; } +QFixed QFontEngine::firstLeftBearing(const QGlyphLayout &glyphs) +{ + for (int i = 0; i < glyphs.numGlyphs; ++i) { + glyph_t glyph = glyphs.glyphs[i]; + glyph_metrics_t gi = boundingBox(glyph); + if (gi.isValid() && gi.width > 0) + return gi.leftBearing(); + } + return 0; +} + QFixed QFontEngine::lastRightBearing(const QGlyphLayout &glyphs) { if (glyphs.numGlyphs >= 1) { @@ -1545,12 +1543,12 @@ glyph_t QFontEngineBox::glyphIndex(uint ucs4) const return 1; } -bool QFontEngineBox::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const +int QFontEngineBox::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const { Q_ASSERT(glyphs->numGlyphs >= *nglyphs); if (*nglyphs < len) { *nglyphs = len; - return false; + return -1; } int ucs4Length = 0; @@ -1566,7 +1564,7 @@ bool QFontEngineBox::stringToCMap(const QChar *str, int len, QGlyphLayout *glyph if (!(flags & GlyphIndicesOnly)) recalcAdvances(glyphs, flags); - return true; + return *nglyphs; } void QFontEngineBox::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags) const @@ -1733,7 +1731,7 @@ void QFontEngineMulti::ensureFallbackFamiliesQueried() if (styleHint == QFont::AnyStyle && fontDef.fixedPitch) styleHint = QFont::TypeWriter; - setFallbackFamiliesList(qt_fallbacksForFamily(fontDef.families.first(), + setFallbackFamiliesList(qt_fallbacksForFamily(fontDef.families.constFirst(), QFont::Style(fontDef.style), styleHint, QChar::Script(m_script))); } @@ -1749,7 +1747,7 @@ void QFontEngineMulti::setFallbackFamiliesList(const QStringList &fallbackFamili QFontEngine *engine = m_engines.at(0); engine->ref.ref(); m_engines[1] = engine; - m_fallbackFamilies << fontDef.families.first(); + m_fallbackFamilies << fontDef.families.constFirst(); } else { m_engines.resize(m_fallbackFamilies.size() + 1); } @@ -1796,11 +1794,7 @@ QFontEngine *QFontEngineMulti::loadEngine(int at) glyph_t QFontEngineMulti::glyphIndex(uint ucs4) const { glyph_t glyph = engine(0)->glyphIndex(ucs4); - if (glyph == 0 - && ucs4 != QChar::LineSeparator - && ucs4 != QChar::LineFeed - && ucs4 != QChar::CarriageReturn - && ucs4 != QChar::ParagraphSeparator) { + if (glyph == 0 && !isIgnorableChar(ucs4)) { if (!m_fallbackFamiliesQueried) const_cast<QFontEngineMulti *>(this)->ensureFallbackFamiliesQueried(); for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) { @@ -1827,17 +1821,60 @@ glyph_t QFontEngineMulti::glyphIndex(uint ucs4) const return glyph; } -bool QFontEngineMulti::stringToCMap(const QChar *str, int len, - QGlyphLayout *glyphs, int *nglyphs, - QFontEngine::ShaperFlags flags) const +int QFontEngineMulti::stringToCMap(const QChar *str, int len, + QGlyphLayout *glyphs, int *nglyphs, + QFontEngine::ShaperFlags flags) const { - if (!engine(0)->stringToCMap(str, len, glyphs, nglyphs, flags)) - return false; + const int originalNumGlyphs = glyphs->numGlyphs; + int mappedGlyphCount = engine(0)->stringToCMap(str, len, glyphs, nglyphs, flags); + if (mappedGlyphCount < 0) + return -1; + // If ContextFontMerging is set and the match for the string was incomplete, we try all + // fallbacks on the full string until we find the best match. + bool contextFontMerging = mappedGlyphCount < *nglyphs && (fontDef.styleStrategy & QFont::ContextFontMerging); + if (contextFontMerging) { + QVarLengthGlyphLayoutArray tempLayout(len); + if (!m_fallbackFamiliesQueried) + const_cast<QFontEngineMulti *>(this)->ensureFallbackFamiliesQueried(); + + int maxGlyphCount = 0; + uchar engineIndex = 0; + for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) { + int numGlyphs = len; + const_cast<QFontEngineMulti *>(this)->ensureEngineAt(x); + maxGlyphCount = engine(x)->stringToCMap(str, len, &tempLayout, &numGlyphs, flags); + + // If we found a better match, we copy data into the main QGlyphLayout + if (maxGlyphCount > mappedGlyphCount) { + *nglyphs = numGlyphs; + glyphs->numGlyphs = originalNumGlyphs; + glyphs->copy(&tempLayout); + engineIndex = x; + if (maxGlyphCount == numGlyphs) + break; + } + } + + if (engineIndex > 0) { + for (int y = 0; y < glyphs->numGlyphs; ++y) { + if (glyphs->glyphs[y] != 0) + glyphs->glyphs[y] |= (engineIndex << 24); + } + } else { + contextFontMerging = false; + } + + mappedGlyphCount = maxGlyphCount; + } + + // Fill in missing glyphs by going through string one character at the time and finding + // the first viable fallback. int glyph_pos = 0; QStringIterator it(str, str + len); int lastFallback = -1; + char32_t previousUcs4 = 0; while (it.hasNext()) { const char32_t ucs4 = it.peekNext(); @@ -1863,14 +1900,10 @@ bool QFontEngineMulti::stringToCMap(const QChar *str, int len, lastFallback = -1; } - if (glyphs->glyphs[glyph_pos] == 0 - && ucs4 != QChar::LineSeparator - && ucs4 != QChar::LineFeed - && ucs4 != QChar::CarriageReturn - && ucs4 != QChar::ParagraphSeparator) { + if (glyphs->glyphs[glyph_pos] == 0 && !isIgnorableChar(ucs4)) { if (!m_fallbackFamiliesQueried) const_cast<QFontEngineMulti *>(this)->ensureFallbackFamiliesQueried(); - for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) { + for (int x = contextFontMerging ? 0 : 1, n = qMin(m_engines.size(), 256); x < n; ++x) { QFontEngine *engine = m_engines.at(x); if (!engine) { if (!shouldLoadFontEngineForCharacter(x, ucs4)) @@ -1899,16 +1932,55 @@ bool QFontEngineMulti::stringToCMap(const QChar *str, int len, break; } } + + // For variant-selectors, they are modifiers to the previous character. If we + // end up with different font selections for the selector and the character it + // modifies, we try applying the selector font to the preceding character as well + const int variantSelectorBlock = 0xFE00; + if ((ucs4 & 0xFFF0) == variantSelectorBlock && glyph_pos > 0) { + int selectorFontEngine = glyphs->glyphs[glyph_pos] >> 24; + int precedingCharacterFontEngine = glyphs->glyphs[glyph_pos - 1] >> 24; + + if (selectorFontEngine != precedingCharacterFontEngine) { + // Emoji variant selectors are specially handled and should affect font + // selection. If VS-16 is used, then this means we want to select a color + // font. If the selected font is already a color font, we do not need search + // again. If the VS-15 is used, then this means we want to select a non-color + // font. If the selected font is not a color font, we don't do anything. + const QFontEngine *selectedEngine = m_engines.at(precedingCharacterFontEngine); + const bool colorFont = selectedEngine->isColorFont(); + const char32_t vs15 = 0xFE0E; + const char32_t vs16 = 0xFE0F; + bool adaptVariantSelector = ucs4 < vs15 + || (ucs4 == vs15 && colorFont) + || (ucs4 == vs16 && !colorFont); + + if (adaptVariantSelector) { + QFontEngine *engine = m_engines.at(selectorFontEngine); + glyph_t glyph = engine->glyphIndex(previousUcs4); + if (glyph != 0) { + glyphs->glyphs[glyph_pos - 1] = glyph; + if (!(flags & GlyphIndicesOnly)) { + QGlyphLayout g = glyphs->mid(glyph_pos - 1, 1); + engine->recalcAdvances(&g, flags); + } + + // set the high byte to indicate which engine the glyph came from + glyphs->glyphs[glyph_pos - 1] |= (selectorFontEngine << 24); + } + } + } + } } it.advance(); ++glyph_pos; + previousUcs4 = ucs4; } *nglyphs = glyph_pos; glyphs->numGlyphs = glyph_pos; - - return true; + return mappedGlyphCount; } bool QFontEngineMulti::shouldLoadFontEngineForCharacter(int at, uint ucs4) const @@ -2200,7 +2272,7 @@ bool QFontEngineMulti::canRender(const QChar *string, int len) const QGlyphLayout g; g.numGlyphs = nglyphs; g.glyphs = glyphs.data(); - if (!stringToCMap(string, len, &g, &nglyphs, GlyphIndicesOnly)) + if (stringToCMap(string, len, &g, &nglyphs, GlyphIndicesOnly) < 0) Q_UNREACHABLE(); for (int i = 0; i < nglyphs; i++) { |