diff options
author | Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io> | 2017-07-03 11:30:43 +0200 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io> | 2017-07-26 10:51:00 +0000 |
commit | 73176d2922baae1ccaa702e4900b0473071d0a96 (patch) | |
tree | fc049297630455d6ea0b7f4161acfba610aed2d1 | |
parent | 8719660416986702c1ee9f65c4e347efa37ac770 (diff) |
Add API to disable text shaping on fonts
In the past, we had an undocumented text flag that worked with
one of the QPainter::drawText() overloads. This was never intended
as public API and served a specific cause in Qt WebKit at one point.
But there is a general need for such API, as disabling shaping features
easily gives 25% performance improvement on text rendering even for
fairly short strings.
This patch adds a new style strategy flag to disable shaping and
will just uses the CMAP and HDMX tables to get glyph indices and advances
for the characters. In Qt 6, the TextBypassShaping flag can be removed
completely and be replaced by the style strategy.
[ChangeLog][QtGui][Text] Added QFont::PreferNoShaping style strategy to support
improvements to performance at the expense of some cosmetic font features.
Task-number: QTBUG-56728
Change-Id: I48e025dcc06afe02824bf5b5011702a7e0036f6d
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
-rw-r--r-- | src/corelib/global/qnamespace.h | 7 | ||||
-rw-r--r-- | src/gui/painting/qpainter.cpp | 2 | ||||
-rw-r--r-- | src/gui/text/qfont.cpp | 5 | ||||
-rw-r--r-- | src/gui/text/qfont.h | 1 | ||||
-rw-r--r-- | src/gui/text/qfontengine.cpp | 4 | ||||
-rw-r--r-- | src/gui/text/qfontengine_p.h | 6 | ||||
-rw-r--r-- | src/gui/text/qfontmetrics.cpp | 2 | ||||
-rw-r--r-- | src/gui/text/qtextengine.cpp | 85 | ||||
-rw-r--r-- | tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp | 6 | ||||
-rw-r--r-- | tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp | 60 |
10 files changed, 144 insertions, 34 deletions
diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index 9056eab2f1..fa3d5913b5 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -241,8 +241,11 @@ public: TextForceRightToLeft = 0x40000, // Ensures that the longest variant is always used when computing the // size of a multi-variant string. - TextLongestVariant = 0x80000, - TextBypassShaping = 0x100000 + TextLongestVariant = 0x80000 + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + , TextBypassShaping = 0x100000 +#endif }; enum TextElideMode { diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp index b186182c34..fb3c845e77 100644 --- a/src/gui/painting/qpainter.cpp +++ b/src/gui/painting/qpainter.cpp @@ -5850,6 +5850,7 @@ void QPainter::drawText(const QPointF &p, const QString &str, int tf, int justif if (!d->engine || str.isEmpty() || pen().style() == Qt::NoPen) return; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (tf & Qt::TextBypassShaping) { // Skip complex shaping, shape using glyph advances only int len = str.length(); @@ -5863,6 +5864,7 @@ void QPainter::drawText(const QPointF &p, const QString &str, int tf, int justif drawTextItem(p, gf); return; } +#endif QStackTextEngine engine(str, d->state->font); engine.option.setTextDirection(d->state->layoutDirection); diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index 7f3ed3adaa..f0a5053196 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -1312,6 +1312,11 @@ QFont::StyleHint QFont::styleHint() const looking font that contains the character. The NoFontMerging flag disables this feature. Please note that enabling this flag will not prevent Qt from automatically picking a suitable font when the selected font does not support the writing system of the text. + \value PreferNoShaping Sometimes, a font will apply complex rules to a set of characters in + order to display them correctly. In some writing systems, such as Brahmic scripts, this is + required in order for the text to be legible, but in e.g. Latin script, it is merely + a cosmetic feature. The PreferNoShaping flag will disable all such features when they + are not required, which will improve performance in most cases. Any of these may be OR-ed with one of these flags: diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h index 0f2bef9152..9c250c82c3 100644 --- a/src/gui/text/qfont.h +++ b/src/gui/text/qfont.h @@ -83,6 +83,7 @@ public: OpenGLCompatible = 0x0200, ForceIntegerMetrics = 0x0400, NoSubpixelAntialias = 0x0800, + PreferNoShaping = 0x1000, NoFontMerging = 0x8000 }; Q_ENUM(StyleStrategy) diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 596c79fd05..e4f9b8b9d4 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -359,10 +359,8 @@ bool QFontEngine::supportsScript(QChar::Script script) const // ### TODO: This only works for scripts that require OpenType. More generally // for scripts that do not require OpenType we should just look at the list of // supported writing systems in the font's OS/2 table. - if (!((script >= QChar::Script_Syriac && script <= QChar::Script_Sinhala) - || script == QChar::Script_Khmer || script == QChar::Script_Nko)) { + if (!scriptRequiresOpenType(script)) return true; - } #if QT_CONFIG(harfbuzz) if (qt_useHarfbuzzNG()) { diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index 514e9424b2..1ae2e86a93 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -242,6 +242,12 @@ public: void *harfbuzzFace() const; bool supportsScript(QChar::Script script) const; + inline static bool scriptRequiresOpenType(QChar::Script script) + { + return ((script >= QChar::Script_Syriac && script <= QChar::Script_Sinhala) + || script == QChar::Script_Khmer || script == QChar::Script_Nko); + } + virtual int getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints); void clearGlyphCache(const void *key); diff --git a/src/gui/text/qfontmetrics.cpp b/src/gui/text/qfontmetrics.cpp index 8067969f56..5675d6921d 100644 --- a/src/gui/text/qfontmetrics.cpp +++ b/src/gui/text/qfontmetrics.cpp @@ -541,6 +541,7 @@ int QFontMetrics::width(const QString &text, int len, int flags) const if (len == 0) return 0; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (flags & Qt::TextBypassShaping) { // Skip complex shaping, only use advances int numGlyphs = len; @@ -554,6 +555,7 @@ int QFontMetrics::width(const QString &text, int len, int flags) const width += glyphs.advances[i]; return qRound(width); } +#endif QStackTextEngine layout(text, QFont(d.data())); return qRound(layout.width(0, len)); diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 4d24fb50af..a9d23d8e4b 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -1005,20 +1005,53 @@ void QTextEngine::shapeText(int item) const QFontEngine *fontEngine = this->fontEngine(si, &si.ascent, &si.descent, &si.leading); + bool kerningEnabled; + bool letterSpacingIsAbsolute; + bool shapingEnabled; + QFixed letterSpacing, wordSpacing; +#ifndef QT_NO_RAWFONT + if (useRawFont) { + QTextCharFormat f = format(&si); + kerningEnabled = f.fontKerning(); + shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script)) + || (f.fontStyleStrategy() & QFont::PreferNoShaping) == 0; + wordSpacing = QFixed::fromReal(f.fontWordSpacing()); + letterSpacing = QFixed::fromReal(f.fontLetterSpacing()); + letterSpacingIsAbsolute = true; + } else +#endif + { + QFont font = this->font(si); + kerningEnabled = font.d->kerning; + shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script)) + || (font.d->request.styleStrategy & QFont::PreferNoShaping) == 0; + letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute; + letterSpacing = font.d->letterSpacing; + wordSpacing = font.d->wordSpacing; + + if (letterSpacingIsAbsolute && letterSpacing.value()) + letterSpacing *= font.d->dpi / qt_defaultDpiY(); + } + // split up the item into parts that come from different font engines // k * 3 entries, array[k] == index in string, array[k + 1] == index in glyphs, array[k + 2] == engine index QVector<uint> itemBoundaries; itemBoundaries.reserve(24); - if (fontEngine->type() == QFontEngine::Multi) { + + QGlyphLayout initialGlyphs = availableGlyphs(&si); + int nGlyphs = initialGlyphs.numGlyphs; + if (fontEngine->type() == QFontEngine::Multi || !shapingEnabled) { // ask the font engine to find out which glyphs (as an index in the specific font) // to use for the text in one item. - QGlyphLayout initialGlyphs = availableGlyphs(&si); - - int nGlyphs = initialGlyphs.numGlyphs; - QFontEngine::ShaperFlags shaperFlags(QFontEngine::GlyphIndicesOnly); + QFontEngine::ShaperFlags shaperFlags = + shapingEnabled + ? QFontEngine::GlyphIndicesOnly + : QFontEngine::ShaperFlag(0); if (!fontEngine->stringToCMap(reinterpret_cast<const QChar *>(string), itemLength, &initialGlyphs, &nGlyphs, shaperFlags)) Q_UNREACHABLE(); + } + if (fontEngine->type() == QFontEngine::Multi) { uint lastEngine = ~0u; for (int i = 0, glyph_pos = 0; i < itemLength; ++i, ++glyph_pos) { const uint engineIdx = initialGlyphs.glyphs[glyph_pos] >> 24; @@ -1046,35 +1079,29 @@ void QTextEngine::shapeText(int item) const itemBoundaries.append(0); } - bool kerningEnabled; - bool letterSpacingIsAbsolute; - QFixed letterSpacing, wordSpacing; -#ifndef QT_NO_RAWFONT - if (useRawFont) { - QTextCharFormat f = format(&si); - kerningEnabled = f.fontKerning(); - wordSpacing = QFixed::fromReal(f.fontWordSpacing()); - letterSpacing = QFixed::fromReal(f.fontLetterSpacing()); - letterSpacingIsAbsolute = true; - } else -#endif - { - QFont font = this->font(si); - kerningEnabled = font.d->kerning; - letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute; - letterSpacing = font.d->letterSpacing; - wordSpacing = font.d->wordSpacing; + if (Q_UNLIKELY(!shapingEnabled)) { + ushort *log_clusters = logClusters(&si); - if (letterSpacingIsAbsolute && letterSpacing.value()) - letterSpacing *= font.d->dpi / qt_defaultDpiY(); - } + int glyph_pos = 0; + for (int i = 0; i < itemLength; ++i, ++glyph_pos) { + log_clusters[i] = glyph_pos; + initialGlyphs.attributes[glyph_pos].clusterStart = true; + if (QChar::isHighSurrogate(string[i]) + && i + 1 < itemLength + && QChar::isLowSurrogate(string[i + 1])) { + ++i; + log_clusters[i] = glyph_pos; + } + } + si.num_glyphs = glyph_pos; #if QT_CONFIG(harfbuzz) - if (Q_LIKELY(qt_useHarfbuzzNG())) + } else if (Q_LIKELY(qt_useHarfbuzzNG())) { si.num_glyphs = shapeTextWithHarfbuzzNG(si, string, itemLength, fontEngine, itemBoundaries, kerningEnabled, letterSpacing != 0); - else #endif - si.num_glyphs = shapeTextWithHarfbuzz(si, string, itemLength, fontEngine, itemBoundaries, kerningEnabled); + } else { + si.num_glyphs = shapeTextWithHarfbuzz(si, string, itemLength, fontEngine, itemBoundaries, kerningEnabled); + } if (Q_UNLIKELY(si.num_glyphs == 0)) { Q_UNREACHABLE(); // ### report shaping errors somehow return; diff --git a/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp b/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp index 8667caa1ef..0a422fca17 100644 --- a/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp +++ b/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp @@ -47,7 +47,11 @@ private slots: void elidedText(); void veryNarrowElidedText(); void averageCharWidth(); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void bypassShaping(); +#endif + void elidedMultiLength(); void elidedMultiLengthF(); void inFontUcs4(); @@ -187,6 +191,7 @@ void tst_QFontMetrics::averageCharWidth() QVERIFY(fmf.averageCharWidth() != 0); } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void tst_QFontMetrics::bypassShaping() { QFont f; @@ -201,6 +206,7 @@ void tst_QFontMetrics::bypassShaping() // This assertion is needed in Qt WebKit's WebCore::Font::offsetForPositionForSimpleText QCOMPARE(textWidth, charsWidth); } +#endif template<class FontMetrics, typename PrimitiveType> void elidedMultiLength_helper() { diff --git a/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp b/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp index ee50b98733..0371f51961 100644 --- a/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp +++ b/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp @@ -78,6 +78,9 @@ private slots: void thaiIsolatedSaraAm(); void thaiWithZWJ(); void thaiMultipleVowels(); + + void shapingDisabledDevanagari(); + void shapingDisabledLatin(); private: bool haveTestFonts; }; @@ -1280,5 +1283,62 @@ void tst_QTextScriptEngine::thaiMultipleVowels() // If we haven't crashed at this point, then the test has passed. } +void tst_QTextScriptEngine::shapingDisabledLatin() +{ + QString s("fi"); + + QFont font("Calibri"); + font.setStyleStrategy(QFont::PreferNoShaping); + + QTextLayout layout(s); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList<QGlyphRun> runs = layout.glyphRuns(); + + QCOMPARE(runs.size(), 1); + QCOMPARE(runs.first().glyphIndexes().size(), 2); +} + +void tst_QTextScriptEngine::shapingDisabledDevanagari() +{ + QString s; + s += QChar(0x0915); // KA + s += QChar(0x094D); // VIRAMA + s += QChar(0x0915); // KA + + + QList<QGlyphRun> normalRuns; + { + QTextLayout layout(s); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + normalRuns = layout.glyphRuns(); + } + + QFont font; + font.setStyleStrategy(QFont::PreferNoShaping); + + QList<QGlyphRun> noShapingRuns; + { + QTextLayout layout(s); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + noShapingRuns = layout.glyphRuns(); + } + + // Even though shaping is disabled, Devanagari requires it, so the flag should be ignored. + QCOMPARE(normalRuns.size(), 1); + QCOMPARE(noShapingRuns.size(), 1); + QCOMPARE(noShapingRuns.first().glyphIndexes().size(), normalRuns.first().glyphIndexes().size()); +} + QTEST_MAIN(tst_QTextScriptEngine) #include "tst_qtextscriptengine.moc" |