From 1674e67f329da226f32c8fcd9fe64ec8f9e152a8 Mon Sep 17 00:00:00 2001 From: kleuter Date: Sun, 17 Oct 2021 11:19:03 +0200 Subject: [PATCH] Qt 5.15.2: qfontengine original --- 5.15.2/qtbase/src/gui/text/qfontengine.cpp | 2347 ++++++++++++++++++++ 1 file changed, 2347 insertions(+) create mode 100644 5.15.2/qtbase/src/gui/text/qfontengine.cpp diff --git a/5.15.2/qtbase/src/gui/text/qfontengine.cpp b/5.15.2/qtbase/src/gui/text/qfontengine.cpp new file mode 100644 index 0000000..f1fd755 --- /dev/null +++ b/5.15.2/qtbase/src/gui/text/qfontengine.cpp @@ -0,0 +1,2347 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include "qbitmap.h" +#include "qpainter.h" +#include "qpainterpath.h" +#include "qvarlengtharray.h" +#include +#include +#include + +#if QT_CONFIG(harfbuzz) +# include "qharfbuzzng_p.h" +# include +#endif +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +static inline bool qtransform_equals_no_translate(const QTransform &a, const QTransform &b) +{ + if (a.type() <= QTransform::TxTranslate && b.type() <= QTransform::TxTranslate) { + return true; + } else { + // We always use paths for perspective text anyway, so no + // point in checking the full matrix... + Q_ASSERT(a.type() < QTransform::TxProject); + Q_ASSERT(b.type() < QTransform::TxProject); + + return a.m11() == b.m11() + && a.m12() == b.m12() + && a.m21() == b.m21() + && a.m22() == b.m22(); + } +} + +template +static inline bool qSafeFromBigEndian(const uchar *source, const uchar *end, T *output) +{ + if (source + sizeof(T) > end) + return false; + + *output = qFromBigEndian(source); + 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 + +Q_STATIC_ASSERT(sizeof(HB_Glyph) == sizeof(glyph_t)); +Q_STATIC_ASSERT(sizeof(HB_Fixed) == sizeof(QFixed)); + +static HB_Bool hb_stringToGlyphs(HB_Font font, const HB_UChar16 *string, hb_uint32 length, HB_Glyph *glyphs, hb_uint32 *numGlyphs, HB_Bool rightToLeft) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + const QChar *str = reinterpret_cast(string); + + QGlyphLayout qglyphs; + qglyphs.numGlyphs = *numGlyphs; + qglyphs.glyphs = glyphs; + int nGlyphs = *numGlyphs; + bool result = fe->stringToCMap(str, length, &qglyphs, &nGlyphs, QFontEngine::GlyphIndicesOnly); + *numGlyphs = nGlyphs; + + if (rightToLeft && result && !fe->symbol) { + QStringIterator it(str, str + length); + while (it.hasNext()) { + const uint ucs4 = it.next(); + const uint mirrored = QChar::mirroredChar(ucs4); + if (Q_UNLIKELY(mirrored != ucs4)) + *glyphs = fe->glyphIndex(mirrored); + ++glyphs; + } + } + + return result; +} + +static void hb_getAdvances(HB_Font font, const HB_Glyph *glyphs, hb_uint32 numGlyphs, HB_Fixed *advances, int flags) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QGlyphLayout qglyphs; + qglyphs.numGlyphs = numGlyphs; + qglyphs.glyphs = const_cast(glyphs); + qglyphs.advances = reinterpret_cast(advances); + + fe->recalcAdvances(&qglyphs, (flags & HB_ShaperFlag_UseDesignMetrics) ? QFontEngine::DesignMetrics : QFontEngine::ShaperFlags{}); +} + +static HB_Bool hb_canRender(HB_Font font, const HB_UChar16 *string, hb_uint32 length) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->canRender(reinterpret_cast(string), length); +} + +static void hb_getGlyphMetrics(HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + glyph_metrics_t m = fe->boundingBox(glyph); + metrics->x = m.x.value(); + metrics->y = m.y.value(); + metrics->width = m.width.value(); + metrics->height = m.height.value(); + metrics->xOffset = m.xoff.value(); + metrics->yOffset = m.yoff.value(); +} + +static HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric) +{ + if (metric == HB_FontAscent) { + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->ascent().value(); + } + return 0; +} + +int QFontEngine::getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints) +{ + Q_UNUSED(glyph) + Q_UNUSED(flags) + Q_UNUSED(point) + Q_UNUSED(xpos) + Q_UNUSED(ypos) + Q_UNUSED(nPoints) + return Err_Not_Covered; +} + +static HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return (HB_Error)fe->getPointInOutline(glyph, flags, point, (QFixed *)xpos, (QFixed *)ypos, (quint32 *)nPoints); +} + +static const HB_FontClass hb_fontClass = { + hb_stringToGlyphs, hb_getAdvances, hb_canRender, hb_getPointInOutline, + hb_getGlyphMetrics, hb_getFontMetric +}; + +static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length) +{ + QFontEngine::FaceData *data = (QFontEngine::FaceData *)font; + Q_ASSERT(data); + + qt_get_font_table_func_t get_font_table = data->get_font_table; + Q_ASSERT(get_font_table); + + if (!get_font_table(data->user_data, tableTag, buffer, length)) + return HB_Err_Invalid_Argument; + return HB_Err_Ok; +} + +static void hb_freeFace(void *face) +{ + qHBFreeFace((HB_Face)face); +} + + +static bool qt_get_font_table_default(void *user_data, uint tag, uchar *buffer, uint *length) +{ + QFontEngine *fe = (QFontEngine *)user_data; + return fe->getSfntTableData(tag, buffer, length); +} + + +#ifdef QT_BUILD_INTERNAL +// for testing purpose only, not thread-safe! +static QList *enginesCollector = nullptr; + +Q_AUTOTEST_EXPORT void QFontEngine_startCollectingEngines() +{ + delete enginesCollector; + enginesCollector = new QList(); +} + +Q_AUTOTEST_EXPORT QList QFontEngine_stopCollectingEngines() +{ + Q_ASSERT(enginesCollector); + QList ret = *enginesCollector; + delete enginesCollector; + enginesCollector = nullptr; + return ret; +} +#endif // QT_BUILD_INTERNAL + + +// QFontEngine + +#define kBearingNotInitialized std::numeric_limits::max() + +QFontEngine::QFontEngine(Type type) + : m_type(type), ref(0), + font_(), + face_(), + m_minLeftBearing(kBearingNotInitialized), + m_minRightBearing(kBearingNotInitialized) +{ + faceData.user_data = this; + faceData.get_font_table = qt_get_font_table_default; + + cache_cost = 0; + fsType = 0; + symbol = false; + isSmoothlyScalable = false; + + glyphFormat = Format_None; + m_subPixelPositionCount = 0; + +#ifdef QT_BUILD_INTERNAL + if (enginesCollector) + enginesCollector->append(this); +#endif +} + +QFontEngine::~QFontEngine() +{ +#ifdef QT_BUILD_INTERNAL + if (enginesCollector) + enginesCollector->removeOne(this); +#endif +} + +QFixed QFontEngine::lineThickness() const +{ + // ad hoc algorithm + int score = fontDef.weight * fontDef.pixelSize; + int lw = score / 700; + + // looks better with thicker line for small pointsizes + if (lw < 2 && score >= 1050) lw = 2; + if (lw == 0) lw = 1; + + return lw; +} + +QFixed QFontEngine::underlinePosition() const +{ + return ((lineThickness() * 2) + 3) / 6; +} + +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(this)); +#endif + if (!font_) { + HB_Face hbFace = (HB_Face)harfbuzzFace(); + if (hbFace->font_for_init) { + void *data = hbFace->font_for_init; + q_check_ptr(qHBLoadFace(hbFace)); + free(data); + } + + HB_FontRec *hbFont = (HB_FontRec *) malloc(sizeof(HB_FontRec)); + Q_CHECK_PTR(hbFont); + hbFont->klass = &hb_fontClass; + hbFont->userData = const_cast(this); + + qint64 emSquare = emSquareSize().truncate(); + Q_ASSERT(emSquare == emSquareSize().toInt()); // ensure no truncation + if (emSquare == 0) + emSquare = 1000; // a fallback value suitable for Type1 fonts + hbFont->y_ppem = fontDef.pixelSize; + hbFont->x_ppem = fontDef.pixelSize * fontDef.stretch / 100; + // same as QFixed(x)/QFixed(emSquare) but without int32 overflow for x + hbFont->x_scale = (((qint64)hbFont->x_ppem << 6) * 0x10000L + (emSquare >> 1)) / emSquare; + hbFont->y_scale = (((qint64)hbFont->y_ppem << 6) * 0x10000L + (emSquare >> 1)) / emSquare; + + font_ = Holder(hbFont, free); + } + return font_.get(); +} + +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(this)); +#endif + if (!face_) { + QFontEngine::FaceData *data = (QFontEngine::FaceData *)malloc(sizeof(QFontEngine::FaceData)); + Q_CHECK_PTR(data); + data->user_data = faceData.user_data; + data->get_font_table = faceData.get_font_table; + + HB_Face hbFace = qHBNewFace(data, hb_getSFntTable); + Q_CHECK_PTR(hbFace); + hbFace->isSymbolFont = symbol; + + face_ = Holder(hbFace, hb_freeFace); + } + return face_.get(); +} + +bool QFontEngine::supportsScript(QChar::Script script) const +{ + if (type() <= QFontEngine::Multi) + return true; + + // ### 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 (!scriptRequiresOpenType(script)) + return true; + +#if QT_CONFIG(harfbuzz) + if (qt_useHarfbuzzNG()) { +#if defined(Q_OS_DARWIN) + // 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'), 0, &lenMort) || getSfntTableData(MAKE_TAG('m','o','r','x'), 0, &lenMorx)) + return true; +#endif + + bool ret = false; + if (hb_face_t *face = hb_qt_face_get_for_engine(const_cast(this))) { + hb_tag_t script_tag_1, script_tag_2; + hb_ot_tags_from_script(hb_qt_script_to_script(script), &script_tag_1, &script_tag_2); + + unsigned int script_index; + ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, script_tag_1, &script_index); + if (!ret) { + ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, script_tag_2, &script_index); + if (!ret && script_tag_2 != HB_OT_TAG_DEFAULT_SCRIPT) + ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, HB_OT_TAG_DEFAULT_SCRIPT, &script_index); + } + } + return ret; + } +#endif + HB_Face hbFace = (HB_Face)harfbuzzFace(); + if (hbFace->font_for_init) { + void *data = hbFace->font_for_init; + q_check_ptr(qHBLoadFace(hbFace)); + free(data); + } + return hbFace->supported_scripts[script_to_hbscript(script)]; +} + +bool QFontEngine::canRender(const QChar *str, int len) const +{ + QStringIterator it(str, str + len); + while (it.hasNext()) { + if (glyphIndex(it.next()) == 0) + return false; + } + + return true; +} + +glyph_metrics_t QFontEngine::boundingBox(glyph_t glyph, const QTransform &matrix) +{ + glyph_metrics_t metrics = boundingBox(glyph); + + if (matrix.type() > QTransform::TxTranslate) { + return metrics.transformed(matrix); + } + return metrics; +} + +QFixed QFontEngine::calculatedCapHeight() const +{ + const glyph_t glyph = glyphIndex('H'); + glyph_metrics_t bb = const_cast(this)->boundingBox(glyph); + return bb.height; +} + +QFixed QFontEngine::xHeight() const +{ + const glyph_t glyph = glyphIndex('x'); + glyph_metrics_t bb = const_cast(this)->boundingBox(glyph); + return bb.height; +} + +QFixed QFontEngine::averageCharWidth() const +{ + const glyph_t glyph = glyphIndex('x'); + glyph_metrics_t bb = const_cast(this)->boundingBox(glyph); + return bb.xoff; +} + +bool QFontEngine::supportsTransformation(const QTransform &transform) const +{ + return transform.type() < QTransform::TxProject; +} + +bool QFontEngine::expectsGammaCorrectedBlending() const +{ + return true; +} + +void QFontEngine::getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, + QVarLengthArray &glyphs_out, QVarLengthArray &positions) +{ + QFixed xpos; + QFixed ypos; + + const bool transform = matrix.m11() != 1. + || matrix.m12() != 0. + || matrix.m21() != 0. + || matrix.m22() != 1.; + if (!transform) { + xpos = QFixed::fromReal(matrix.dx()); + ypos = QFixed::fromReal(matrix.dy()); + } + + int current = 0; + if (flags & QTextItem::RightToLeft) { + int i = glyphs.numGlyphs; + int totalKashidas = 0; + while(i--) { + if (glyphs.attributes[i].dontPrint) + continue; + xpos += glyphs.advances[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + totalKashidas += glyphs.justifications[i].nKashidas; + } + positions.resize(glyphs.numGlyphs+totalKashidas); + glyphs_out.resize(glyphs.numGlyphs+totalKashidas); + + i = 0; + while(i < glyphs.numGlyphs) { + if (glyphs.attributes[i].dontPrint) { + ++i; + continue; + } + xpos -= glyphs.advances[i]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = glyphs.glyphs[i]; + ++current; + if (glyphs.justifications[i].nKashidas) { + QChar ch(0x640); // Kashida character + + glyph_t kashidaGlyph = glyphIndex(ch.unicode()); + QFixed kashidaWidth; + + QGlyphLayout g; + g.numGlyphs = 1; + g.glyphs = &kashidaGlyph; + g.advances = &kashidaWidth; + recalcAdvances(&g, { }); + + for (uint k = 0; k < glyphs.justifications[i].nKashidas; ++k) { + xpos -= kashidaWidth; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = kashidaGlyph; + ++current; + } + } else { + xpos -= QFixed::fromFixed(glyphs.justifications[i].space_18d6); + } + ++i; + } + } else { + positions.resize(glyphs.numGlyphs); + glyphs_out.resize(glyphs.numGlyphs); + int i = 0; + if (!transform) { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + positions[current].x = xpos + glyphs.offsets[i].x; + positions[current].y = ypos + glyphs.offsets[i].y; + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ++current; + } + ++i; + } + } else { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + positions[current].x = QFixed::fromReal(gpos.x()); + positions[current].y = QFixed::fromReal(gpos.y()); + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ++current; + } + ++i; + } + } + } + positions.resize(current); + glyphs_out.resize(current); + Q_ASSERT(positions.size() == glyphs_out.size()); +} + +void QFontEngine::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + glyph_metrics_t gi = boundingBox(glyph); + if (leftBearing != nullptr) + *leftBearing = gi.leftBearing().toReal(); + if (rightBearing != nullptr) + *rightBearing = gi.rightBearing().toReal(); +} + +qreal QFontEngine::minLeftBearing() const +{ + if (m_minLeftBearing == kBearingNotInitialized) + minRightBearing(); // Initializes both (see below) + + return m_minLeftBearing; +} + +#define q16Dot16ToFloat(i) ((i) / 65536.0) + +#define kMinLeftSideBearingOffset 12 +#define kMinRightSideBearingOffset 14 + +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')); + if (hheaTable.size() >= int(kMinRightSideBearingOffset + sizeof(qint16))) { + const uchar *tableData = reinterpret_cast(hheaTable.constData()); + Q_ASSERT(q16Dot16ToFloat(qFromBigEndian(tableData)) == 1.0); + + qint16 minLeftSideBearing = qFromBigEndian(tableData + kMinLeftSideBearingOffset); + qint16 minRightSideBearing = qFromBigEndian(tableData + kMinRightSideBearingOffset); + + // The table data is expressed as FUnits, meaning we have to take the number + // of units per em into account. Since pixelSize already has taken DPI into + // account we can use that directly instead of the point size. + int unitsPerEm = emSquareSize().toInt(); + qreal funitToPixelFactor = fontDef.pixelSize / unitsPerEm; + + // Some fonts on OS X (such as Gurmukhi Sangam MN, Khmer MN, Lao Sangam MN, etc.), have + // invalid values for their NBSPACE left bearing, causing the 'hhea' minimum bearings to + // be way off. We detect this by assuming that the minimum bearsings are within a certain + // range of the em square size. + static const int largestValidBearing = 4 * unitsPerEm; + + if (qAbs(minLeftSideBearing) < largestValidBearing) + m_minLeftBearing = minLeftSideBearing * funitToPixelFactor; + if (qAbs(minRightSideBearing) < largestValidBearing) + m_minRightBearing = minRightSideBearing * funitToPixelFactor; + } + + // Fallback in case of missing 'hhea' table (bitmap fonts e.g.) or broken 'hhea' values + if (m_minLeftBearing == kBearingNotInitialized || m_minRightBearing == kBearingNotInitialized) { + + // To balance performance and correctness we only look at a subset of the + // possible glyphs in the font, based on which characters are more likely + // to have a left or right bearing. + static const ushort characterSubset[] = { + '(', 'C', 'F', 'K', 'V', 'X', 'Y', ']', '_', 'f', 'r', '|', + 127, 205, 645, 884, 922, 1070, 12386 + }; + + // The font may have minimum bearings larger than 0, so we have to start at the max + m_minLeftBearing = m_minRightBearing = std::numeric_limits::max(); + + for (uint i = 0; i < (sizeof(characterSubset) / sizeof(ushort)); ++i) { + const glyph_t glyph = glyphIndex(characterSubset[i]); + if (!glyph) + continue; + + glyph_metrics_t glyphMetrics = const_cast(this)->boundingBox(glyph); + + // Glyphs with no contours shouldn't contribute to bearings + if (!glyphMetrics.width || !glyphMetrics.height) + continue; + + m_minLeftBearing = qMin(m_minLeftBearing, glyphMetrics.leftBearing().toReal()); + m_minRightBearing = qMin(m_minRightBearing, glyphMetrics.rightBearing().toReal()); + } + } + + if (m_minLeftBearing == kBearingNotInitialized || m_minRightBearing == kBearingNotInitialized) + qWarning() << "Failed to compute left/right minimum bearings for" << fontDef.family; + } + + return m_minRightBearing; +} + +glyph_metrics_t QFontEngine::tightBoundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + // If shaping has found this should be ignored, ignore it. + if (!glyphs.advances[i] || glyphs.attributes[i].dontPrint) + continue; + glyph_metrics_t bb = boundingBox(glyphs.glyphs[i]); + QFixed x = overall.xoff + glyphs.offsets[i].x + bb.x; + 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; + overall.yoff += bb.yoff; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + + +void QFontEngine::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, + QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + addGlyphsToPath(positioned_glyphs.data(), positions.data(), positioned_glyphs.size(), path, flags); +} + +#define GRID(x, y) grid[(y)*(w+1) + (x)] +#define SET(x, y) (*(image_data + (y)*bpl + ((x) >> 3)) & (0x80 >> ((x) & 7))) + +enum { EdgeRight = 0x1, + EdgeDown = 0x2, + EdgeLeft = 0x4, + EdgeUp = 0x8 +}; + +static void collectSingleContour(qreal x0, qreal y0, uint *grid, int x, int y, int w, int h, QPainterPath *path) +{ + Q_UNUSED(h); + + path->moveTo(x + x0, y + y0); + while (GRID(x, y)) { + if (GRID(x, y) & EdgeRight) { + while (GRID(x, y) & EdgeRight) { + GRID(x, y) &= ~EdgeRight; + ++x; + } + Q_ASSERT(x <= w); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeDown) { + while (GRID(x, y) & EdgeDown) { + GRID(x, y) &= ~EdgeDown; + ++y; + } + Q_ASSERT(y <= h); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeLeft) { + while (GRID(x, y) & EdgeLeft) { + GRID(x, y) &= ~EdgeLeft; + --x; + } + Q_ASSERT(x >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeUp) { + while (GRID(x, y) & EdgeUp) { + GRID(x, y) &= ~EdgeUp; + --y; + } + Q_ASSERT(y >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + } + path->closeSubpath(); +} + +Q_GUI_EXPORT void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path) +{ + uint *grid = new uint[(w+1)*(h+1)]; + // set up edges + for (int y = 0; y <= h; ++y) { + for (int x = 0; x <= w; ++x) { + bool topLeft = (x == 0 || y == 0) ? false : SET(x - 1, y - 1); + bool topRight = (x == w || y == 0) ? false : SET(x, y - 1); + bool bottomLeft = (x == 0 || y == h) ? false : SET(x - 1, y); + bool bottomRight = (x == w || y == h) ? false : SET(x, y); + + GRID(x, y) = 0; + if ((!topRight) & bottomRight) + GRID(x, y) |= EdgeRight; + if ((!bottomRight) & bottomLeft) + GRID(x, y) |= EdgeDown; + if ((!bottomLeft) & topLeft) + GRID(x, y) |= EdgeLeft; + if ((!topLeft) & topRight) + GRID(x, y) |= EdgeUp; + } + } + + // collect edges + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (!GRID(x, y)) + continue; + // found start of a contour, follow it + collectSingleContour(x0, y0, grid, x, y, w, h, path); + } + } + delete [] grid; +} + +#undef GRID +#undef SET + + +void QFontEngine::addBitmapFontToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ +// TODO what to do with 'flags' ?? + Q_UNUSED(flags); + QFixed advanceX = QFixed::fromReal(x); + QFixed advanceY = QFixed::fromReal(y); + for (int i=0; i < glyphs.numGlyphs; ++i) { + glyph_metrics_t metrics = boundingBox(glyphs.glyphs[i]); + if (metrics.width.value() == 0 || metrics.height.value() == 0) { + advanceX += glyphs.advances[i]; + continue; + } + const QImage alphaMask = alphaMapForGlyph(glyphs.glyphs[i]); + + const int w = alphaMask.width(); + const int h = alphaMask.height(); + const int srcBpl = alphaMask.bytesPerLine(); + QImage bitmap; + if (alphaMask.depth() == 1) { + bitmap = alphaMask; + } else { + bitmap = QImage(w, h, QImage::Format_Mono); + const uchar *imageData = alphaMask.bits(); + const int destBpl = bitmap.bytesPerLine(); + uchar *bitmapData = bitmap.bits(); + + for (int yi = 0; yi < h; ++yi) { + const uchar *src = imageData + yi*srcBpl; + uchar *dst = bitmapData + yi*destBpl; + for (int xi = 0; xi < w; ++xi) { + const int byte = xi / 8; + const int bit = xi % 8; + if (bit == 0) + dst[byte] = 0; + if (src[xi]) + dst[byte] |= 128 >> bit; + } + } + } + const uchar *bitmap_data = bitmap.constBits(); + QFixedPoint offset = glyphs.offsets[i]; + advanceX += offset.x; + advanceY += offset.y; + qt_addBitmapToPath((advanceX + metrics.x).toReal(), (advanceY + metrics.y).toReal(), bitmap_data, bitmap.bytesPerLine(), w, h, path); + advanceX += glyphs.advances[i]; + } +} + +void QFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + qreal x = positions[0].x.toReal(); + qreal y = positions[0].y.toReal(); + QVarLengthGlyphLayoutArray g(nGlyphs); + + for (int i = 0; i < nGlyphs - 1; ++i) { + g.glyphs[i] = glyphs[i]; + g.advances[i] = positions[i + 1].x - positions[i].x; + } + g.glyphs[nGlyphs - 1] = glyphs[nGlyphs - 1]; + g.advances[nGlyphs - 1] = QFixed::fromReal(maxCharWidth()); + + addBitmapFontToPath(x, y, g, path, flags); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed /*subPixelPosition*/) +{ + // For font engines don't support subpixel positioning + return alphaMapForGlyph(glyph); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, const QTransform &t) +{ + QImage i = alphaMapForGlyph(glyph); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t).convertToFormat(QImage::Format_Alpha8); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + + return i; +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) +{ + if (! supportsSubPixelPositions()) + return alphaMapForGlyph(glyph, t); + + QImage i = alphaMapForGlyph(glyph, subPixelPosition); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t).convertToFormat(QImage::Format_Alpha8); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + + return i; +} + +QImage QFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed /*subPixelPosition*/, const QTransform &t) +{ + const QImage alphaMask = alphaMapForGlyph(glyph, t); + QImage rgbMask(alphaMask.width(), alphaMask.height(), QImage::Format_RGB32); + + for (int y=0; y(im.constScanLine(y)); + for (int x=0; x(table.data()), &len)) + return QByteArray(); + return table; +} + +void QFontEngine::clearGlyphCache(const void *context) +{ + m_glyphCaches.remove(context); +} + +void QFontEngine::setGlyphCache(const void *context, QFontEngineGlyphCache *cache) +{ + Q_ASSERT(cache); + + GlyphCaches &caches = m_glyphCaches[context]; + for (auto & e : caches) { + if (cache == e.cache.data()) + return; + } + + // Limit the glyph caches to 4 per context. This covers all 90 degree rotations, + // and limits memory use when there is continuous or random rotation + if (caches.size() == 4) + caches.pop_back(); + + GlyphCacheEntry entry; + entry.cache = cache; + caches.push_front(entry); + +} + +QFontEngineGlyphCache *QFontEngine::glyphCache(const void *context, + GlyphFormat format, + const QTransform &transform, + const QColor &color) const +{ + const QHash::const_iterator caches = m_glyphCaches.constFind(context); + if (caches == m_glyphCaches.cend()) + return nullptr; + + for (auto &e : *caches) { + QFontEngineGlyphCache *cache = e.cache.data(); + if (format == cache->glyphFormat() + && (format != Format_ARGB || color == cache->color()) + && qtransform_equals_no_translate(cache->m_transform, transform)) { + return cache; + } + } + + return nullptr; +} + +static inline QFixed kerning(int left, int right, const QFontEngine::KernPair *pairs, int numPairs) +{ + uint left_right = (left << 16) + right; + + left = 0, right = numPairs - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + if(pairs[middle].left_right == left_right) + return pairs[middle].adjust; + + if (pairs[middle].left_right < left_right) + left = middle + 1; + else + right = middle - 1; + } + return 0; +} + +void QFontEngine::doKerning(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const +{ + int numPairs = kerning_pairs.size(); + if(!numPairs) + return; + + const KernPair *pairs = kerning_pairs.constData(); + + if (flags & DesignMetrics) { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances[i] += kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs); + } else { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances[i] += qRound(kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs)); + } +} + +void QFontEngine::loadKerningPairs(QFixed scalingFactor) +{ + kerning_pairs.clear(); + + QByteArray tab = getSfntTable(MAKE_TAG('k', 'e', 'r', 'n')); + if (tab.isEmpty()) + return; + + const uchar *table = reinterpret_cast(tab.constData()); + const uchar *end = table + tab.size(); + + quint16 version; + if (!qSafeFromBigEndian(table, end, &version)) + return; + + if (version != 0) { +// qDebug("wrong version"); + return; + } + + quint16 numTables; + if (!qSafeFromBigEndian(table + 2, end, &numTables)) + return; + + { + int offset = 4; + for(int i = 0; i < numTables; ++i) { + const uchar *header = table + offset; + + quint16 version; + if (!qSafeFromBigEndian(header, end, &version)) + goto end; + + quint16 length; + if (!qSafeFromBigEndian(header + 2, end, &length)) + goto end; + + quint16 coverage; + if (!qSafeFromBigEndian(header + 4, end, &coverage)) + goto end; + +// qDebug("subtable: version=%d, coverage=%x",version, coverage); + if(version == 0 && coverage == 0x0001) { + if (offset + length > tab.size()) { +// qDebug("length ouf ot bounds"); + goto end; + } + const uchar *data = table + offset + 6; + + quint16 nPairs; + if (!qSafeFromBigEndian(data, end, &nPairs)) + goto end; + + if(nPairs * 6 + 8 > length - 6) { +// qDebug("corrupt table!"); + // corrupt table + goto end; + } + + int off = 8; + for(int i = 0; i < nPairs; ++i) { + QFontEngine::KernPair p; + + quint16 tmp; + if (!qSafeFromBigEndian(data + off, end, &tmp)) + goto end; + + p.left_right = uint(tmp) << 16; + if (!qSafeFromBigEndian(data + off + 2, end, &tmp)) + goto end; + + p.left_right |= tmp; + + if (!qSafeFromBigEndian(data + off + 4, end, &tmp)) + goto end; + + p.adjust = QFixed(int(short(tmp))) / scalingFactor; + kerning_pairs.append(p); + off += 6; + } + } + offset += length; + } + } +end: + std::sort(kerning_pairs.begin(), kerning_pairs.end()); +// for (int i = 0; i < kerning_pairs.count(); ++i) +// qDebug() << 'i' << i << "left_right" << Qt::hex << kerning_pairs.at(i).left_right; +} + + +int QFontEngine::glyphCount() const +{ + QByteArray maxpTable = getSfntTable(MAKE_TAG('m', 'a', 'x', 'p')); + if (maxpTable.size() < 6) + return 0; + + const uchar *source = reinterpret_cast(maxpTable.constData() + 4); + const uchar *end = source + maxpTable.size(); + + quint16 count = 0; + qSafeFromBigEndian(source, end, &count); + return count; +} + +Qt::HANDLE QFontEngine::handle() const +{ + return nullptr; +} + +const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSymbolFont, int *cmapSize) +{ + const uchar *header = table; + const uchar *endPtr = table + tableSize; + + // version check + quint16 version; + if (!qSafeFromBigEndian(header, endPtr, &version) || version != 0) + return nullptr; + + quint16 numTables; + if (!qSafeFromBigEndian(header + 2, endPtr, &numTables)) + return nullptr; + + const uchar *maps = table + 4; + + enum { + Invalid, + AppleRoman, + Symbol, + Unicode11, + Unicode, + MicrosoftUnicode, + MicrosoftUnicodeExtended + }; + + int symbolTable = -1; + int tableToUse = -1; + int score = Invalid; + for (int n = 0; n < numTables; ++n) { + quint16 platformId; + if (!qSafeFromBigEndian(maps + 8 * n, endPtr, &platformId)) + return nullptr; + + quint16 platformSpecificId = 0; + if (!qSafeFromBigEndian(maps + 8 * n + 2, endPtr, &platformSpecificId)) + return nullptr; + + switch (platformId) { + case 0: // Unicode + if (score < Unicode && + (platformSpecificId == 0 || + platformSpecificId == 2 || + platformSpecificId == 3)) { + tableToUse = n; + score = Unicode; + } else if (score < Unicode11 && platformSpecificId == 1) { + tableToUse = n; + score = Unicode11; + } + break; + case 1: // Apple + if (score < AppleRoman && platformSpecificId == 0) { // Apple Roman + tableToUse = n; + score = AppleRoman; + } + break; + case 3: // Microsoft + switch (platformSpecificId) { + case 0: + symbolTable = n; + if (score < Symbol) { + tableToUse = n; + score = Symbol; + } + break; + case 1: + if (score < MicrosoftUnicode) { + tableToUse = n; + score = MicrosoftUnicode; + } + break; + case 0xa: + if (score < MicrosoftUnicodeExtended) { + tableToUse = n; + score = MicrosoftUnicodeExtended; + } + break; + default: + break; + } + default: + break; + } + } + if(tableToUse < 0) + return nullptr; + +resolveTable: + *isSymbolFont = (symbolTable > -1); + + quint32 unicode_table = 0; + if (!qSafeFromBigEndian(maps + 8 * tableToUse + 4, endPtr, &unicode_table)) + return nullptr; + + if (!unicode_table) + return nullptr; + + // get the header of the unicode table + header = table + unicode_table; + + quint16 format; + if (!qSafeFromBigEndian(header, endPtr, &format)) + return nullptr; + + quint32 length; + if (format < 8) { + quint16 tmp; + if (!qSafeFromBigEndian(header + 2, endPtr, &tmp)) + return nullptr; + length = tmp; + } else { + if (!qSafeFromBigEndian(header + 4, endPtr, &length)) + return nullptr; + } + + if (table + unicode_table + length > endPtr) + return nullptr; + *cmapSize = length; + + // To support symbol fonts that contain a unicode table for the symbol area + // we check the cmap tables and fall back to symbol font unless that would + // involve losing information from the unicode table + if (symbolTable > -1 && ((score == Unicode) || (score == Unicode11))) { + const uchar *selectedTable = table + unicode_table; + + // Check that none of the latin1 range are in the unicode table + bool unicodeTableHasLatin1 = false; + for (int uc=0x00; uc<0x100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, length, uc) != 0) { + unicodeTableHasLatin1 = true; + break; + } + } + + // Check that at least one symbol char is in the unicode table + bool unicodeTableHasSymbols = false; + if (!unicodeTableHasLatin1) { + for (int uc=0xf000; uc<0xf100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, length, uc) != 0) { + unicodeTableHasSymbols = true; + break; + } + } + } + + // Fall back to symbol table + if (!unicodeTableHasLatin1 && unicodeTableHasSymbols) { + tableToUse = symbolTable; + score = Symbol; + goto resolveTable; + } + } + + return table + unicode_table; +} + +quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint unicode) +{ + const uchar *end = cmap + cmapSize; + quint16 format; + if (!qSafeFromBigEndian(cmap, end, &format)) + return 0; + + if (format == 0) { + const uchar *ptr = cmap + 6 + unicode; + if (unicode < 256 && ptr < end) + return quint32(*ptr); + } else if (format == 4) { + /* some fonts come with invalid cmap tables, where the last segment + specified end = start = rangeoffset = 0xffff, delta = 0x0001 + Since 0xffff is never a valid Unicode char anyway, we just get rid of the issue + by returning 0 for 0xffff + */ + if(unicode >= 0xffff) + return 0; + + quint16 segCountX2; + if (!qSafeFromBigEndian(cmap + 6, end, &segCountX2)) + return 0; + + const unsigned char *ends = cmap + 14; + + int i = 0; + for (; i < segCountX2/2; ++i) { + quint16 codePoint; + if (!qSafeFromBigEndian(ends + 2 * i, end, &codePoint)) + return 0; + if (codePoint >= unicode) + break; + } + + const unsigned char *idx = ends + segCountX2 + 2 + 2*i; + + quint16 startIndex; + if (!qSafeFromBigEndian(idx, end, &startIndex)) + return 0; + if (startIndex > unicode) + return 0; + + idx += segCountX2; + + quint16 tmp; + if (!qSafeFromBigEndian(idx, end, &tmp)) + return 0; + qint16 idDelta = qint16(tmp); + + idx += segCountX2; + + quint16 idRangeoffset_t; + if (!qSafeFromBigEndian(idx, end, &idRangeoffset_t)) + return 0; + + quint16 glyphIndex; + if (idRangeoffset_t) { + quint16 id; + if (!qSafeFromBigEndian(idRangeoffset_t + 2 * (unicode - startIndex) + idx, end, &id)) + return 0; + + if (id) + glyphIndex = (idDelta + id) % 0x10000; + else + glyphIndex = 0; + } else { + glyphIndex = (idDelta + unicode) % 0x10000; + } + return glyphIndex; + } else if (format == 6) { + quint16 tableSize; + if (!qSafeFromBigEndian(cmap + 2, end, &tableSize)) + return 0; + + quint16 firstCode6; + if (!qSafeFromBigEndian(cmap + 6, end, &firstCode6)) + return 0; + if (unicode < firstCode6) + return 0; + + quint16 entryCount6; + if (!qSafeFromBigEndian(cmap + 8, end, &entryCount6)) + return 0; + if (entryCount6 * 2 + 10 > tableSize) + return 0; + + quint16 sentinel6 = firstCode6 + entryCount6; + if (unicode >= sentinel6) + return 0; + + quint16 entryIndex6 = unicode - firstCode6; + + quint16 index = 0; + qSafeFromBigEndian(cmap + 10 + (entryIndex6 * 2), end, &index); + return index; + } else if (format == 12) { + quint32 nGroups; + if (!qSafeFromBigEndian(cmap + 12, end, &nGroups)) + return 0; + + cmap += 16; // move to start of groups + + int left = 0, right = nGroups - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + quint32 startCharCode; + if (!qSafeFromBigEndian(cmap + 12 * middle, end, &startCharCode)) + return 0; + + if(unicode < startCharCode) + right = middle - 1; + else { + quint32 endCharCode; + if (!qSafeFromBigEndian(cmap + 12 * middle + 4, end, &endCharCode)) + return 0; + + if (unicode <= endCharCode) { + quint32 index; + if (!qSafeFromBigEndian(cmap + 12 * middle + 8, end, &index)) + return 0; + + return index + unicode - startCharCode; + } + left = middle + 1; + } + } + } else { + qDebug("cmap table of format %d not implemented", format); + } + + return 0; +} + +QByteArray QFontEngine::convertToPostscriptFontFamilyName(const QByteArray &family) +{ + QByteArray f = family; + f.replace(' ', ""); + f.replace('(', ""); + f.replace(')', ""); + f.replace('<', ""); + f.replace('>', ""); + f.replace('[', ""); + f.replace(']', ""); + f.replace('{', ""); + f.replace('}', ""); + f.replace('/', ""); + f.replace('%', ""); + return f; +} + +// Allow font engines (e.g. Windows) that can not reliably create +// outline paths for distance-field rendering to switch the scene +// graph over to native text rendering. +bool QFontEngine::hasUnreliableGlyphOutline() const +{ + // Color glyphs (Emoji) are generally not suited for outlining + return glyphFormat == QFontEngine::Format_ARGB; +} + +QFixed QFontEngine::lastRightBearing(const QGlyphLayout &glyphs, bool round) +{ + if (glyphs.numGlyphs >= 1) { + glyph_t glyph = glyphs.glyphs[glyphs.numGlyphs - 1]; + glyph_metrics_t gi = boundingBox(glyph); + if (gi.isValid()) + return round ? qRound(gi.rightBearing()) : gi.rightBearing(); + } + return 0; +} + + +QFontEngine::GlyphCacheEntry::GlyphCacheEntry() +{ +} + +QFontEngine::GlyphCacheEntry::GlyphCacheEntry(const GlyphCacheEntry &o) + : cache(o.cache) +{ +} + +QFontEngine::GlyphCacheEntry::~GlyphCacheEntry() +{ +} + +QFontEngine::GlyphCacheEntry &QFontEngine::GlyphCacheEntry::operator=(const GlyphCacheEntry &o) +{ + cache = o.cache; + return *this; +} + +// ------------------------------------------------------------------ +// The box font engine +// ------------------------------------------------------------------ + +QFontEngineBox::QFontEngineBox(int size) + : QFontEngine(Box), + _size(size) +{ + cache_cost = sizeof(QFontEngineBox); +} + +QFontEngineBox::QFontEngineBox(Type type, int size) + : QFontEngine(type), + _size(size) +{ + cache_cost = sizeof(QFontEngineBox); +} + +QFontEngineBox::~QFontEngineBox() +{ +} + +glyph_t QFontEngineBox::glyphIndex(uint ucs4) const +{ + Q_UNUSED(ucs4) + return 1; +} + +bool 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; + } + + int ucs4Length = 0; + QStringIterator it(str, str + len); + while (it.hasNext()) { + it.advance(); + glyphs->glyphs[ucs4Length++] = 1; + } + + *nglyphs = ucs4Length; + glyphs->numGlyphs = ucs4Length; + + if (!(flags & GlyphIndicesOnly)) + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineBox::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags) const +{ + for (int i = 0; i < glyphs->numGlyphs; i++) + glyphs->advances[i] = _size; +} + +void QFontEngineBox::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + + QSize s(_size - 3, _size - 3); + for (int k = 0; k < positions.size(); k++) + path->addRect(QRectF(positions[k].toPointF(), s)); +} + +glyph_metrics_t QFontEngineBox::boundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + overall.width = _size*glyphs.numGlyphs; + overall.height = _size; + overall.xoff = overall.width; + return overall; +} + +void QFontEngineBox::draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &ti) +{ + if (!ti.glyphs.numGlyphs) + return; + + // any fixes here should probably also be done in QPaintEnginePrivate::drawBoxTextItem + QSize s(_size - 3, _size - 3); + + QVarLengthArray positions; + QVarLengthArray glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + + QPainter *painter = p->painter(); + painter->save(); + painter->setBrush(Qt::NoBrush); + QPen pen = painter->pen(); + pen.setWidthF(lineThickness().toReal()); + painter->setPen(pen); + for (int k = 0; k < positions.size(); k++) + painter->drawRect(QRectF(positions[k].toPointF(), s)); + painter->restore(); +} + +glyph_metrics_t QFontEngineBox::boundingBox(glyph_t) +{ + return glyph_metrics_t(0, -_size, _size, _size, _size, 0); +} + +QFontEngine *QFontEngineBox::cloneWithSize(qreal pixelSize) const +{ + QFontEngineBox *fe = new QFontEngineBox(pixelSize); + return fe; +} + +QFixed QFontEngineBox::ascent() const +{ + return _size; +} + +QFixed QFontEngineBox::capHeight() const +{ + return _size; +} + +QFixed QFontEngineBox::descent() const +{ + return 0; +} + +QFixed QFontEngineBox::leading() const +{ + QFixed l = _size * QFixed::fromReal(qreal(0.15)); + return l.ceil(); +} + +qreal QFontEngineBox::maxCharWidth() const +{ + return _size; +} + +bool QFontEngineBox::canRender(const QChar *, int) const +{ + return true; +} + +QImage QFontEngineBox::alphaMapForGlyph(glyph_t) +{ + QImage image(_size, _size, QImage::Format_Alpha8); + image.fill(0); + + // FIXME: use qpainter + for (int i=2; i <= _size-3; ++i) { + image.setPixel(i, 2, 255); + image.setPixel(i, _size-3, 255); + image.setPixel(2, i, 255); + image.setPixel(_size-3, i, 255); + } + return image; +} + +// ------------------------------------------------------------------ +// Multi engine +// ------------------------------------------------------------------ + +uchar QFontEngineMulti::highByte(glyph_t glyph) +{ return glyph >> 24; } + +// strip high byte from glyph +static inline glyph_t stripped(glyph_t glyph) +{ return glyph & 0x00ffffff; } + +QFontEngineMulti::QFontEngineMulti(QFontEngine *engine, int script, const QStringList &fallbackFamilies) + : QFontEngine(Multi), + m_fallbackFamilies(fallbackFamilies), + m_script(script), + m_fallbackFamiliesQueried(!m_fallbackFamilies.isEmpty()) +{ + Q_ASSERT(engine && engine->type() != QFontEngine::Multi); + + if (m_fallbackFamilies.isEmpty()) { + // defer obtaining the fallback families until loadEngine(1) + m_fallbackFamilies << QString(); + } + + m_engines.resize(m_fallbackFamilies.size() + 1); + + engine->ref.ref(); + m_engines[0] = engine; + + fontDef = engine->fontDef; + cache_cost = engine->cache_cost; +} + +QFontEngineMulti::~QFontEngineMulti() +{ + for (int i = 0; i < m_engines.size(); ++i) { + QFontEngine *fontEngine = m_engines.at(i); + if (fontEngine && !fontEngine->ref.deref()) + delete fontEngine; + } +} + +QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script); + +void QFontEngineMulti::ensureFallbackFamiliesQueried() +{ + QFont::StyleHint styleHint = QFont::StyleHint(fontDef.styleHint); + if (styleHint == QFont::AnyStyle && fontDef.fixedPitch) + styleHint = QFont::TypeWriter; + + setFallbackFamiliesList(qt_fallbacksForFamily(fontDef.family, QFont::Style(fontDef.style), styleHint, QChar::Script(m_script))); +} + +void QFontEngineMulti::setFallbackFamiliesList(const QStringList &fallbackFamilies) +{ + Q_ASSERT(!m_fallbackFamiliesQueried); + + m_fallbackFamilies = fallbackFamilies; + if (m_fallbackFamilies.isEmpty()) { + // turns out we lied about having any fallback at all + Q_ASSERT(m_engines.size() == 2); // see c-tor for details + QFontEngine *engine = m_engines.at(0); + engine->ref.ref(); + m_engines[1] = engine; + m_fallbackFamilies << fontDef.family; + } else { + m_engines.resize(m_fallbackFamilies.size() + 1); + } + + m_fallbackFamiliesQueried = true; +} + +void QFontEngineMulti::ensureEngineAt(int at) +{ + if (!m_fallbackFamiliesQueried) + ensureFallbackFamiliesQueried(); + Q_ASSERT(at < m_engines.size()); + if (!m_engines.at(at)) { + QFontEngine *engine = loadEngine(at); + if (!engine) + engine = new QFontEngineBox(fontDef.pixelSize); + Q_ASSERT(engine && engine->type() != QFontEngine::Multi); + engine->ref.ref(); + m_engines[at] = engine; + } +} + +QFontEngine *QFontEngineMulti::loadEngine(int at) +{ + QFontDef request(fontDef); + request.styleStrategy |= QFont::NoFontMerging; + request.family = fallbackFamilyAt(at - 1); + request.families = QStringList(request.family); + + // At this point, the main script of the text has already been considered + // when fetching the list of fallback families from the database, and the + // 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 = QFontDatabase::findFont(request, QChar::Script_Common)) { + engine->fontDef.weight = request.weight; + if (request.style > QFont::StyleNormal) + engine->fontDef.style = request.style; + return engine; + } + + return nullptr; +} + +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 (!m_fallbackFamiliesQueried) + const_cast(this)->ensureFallbackFamiliesQueried(); + for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) { + QFontEngine *engine = m_engines.at(x); + if (!engine) { + if (!shouldLoadFontEngineForCharacter(x, ucs4)) + continue; + const_cast(this)->ensureEngineAt(x); + engine = m_engines.at(x); + } + Q_ASSERT(engine != nullptr); + if (engine->type() == Box) + continue; + + glyph = engine->glyphIndex(ucs4); + if (glyph != 0) { + // set the high byte to indicate which engine the glyph came from + glyph |= (x << 24); + break; + } + } + } + + return glyph; +} + +bool 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; + + int glyph_pos = 0; + QStringIterator it(str, str + len); + + int lastFallback = -1; + while (it.hasNext()) { + const uint ucs4 = it.peekNext(); + + // If we applied a fallback font to previous glyph, and the current is either + // ZWJ or ZWNJ, we should also try applying the same fallback font to that, in order + // to get the correct shaping rules applied. + if (lastFallback >= 0 && (ucs4 == QChar(0x200d) || ucs4 == QChar(0x200c))) { + QFontEngine *engine = m_engines.at(lastFallback); + glyph_t glyph = engine->glyphIndex(ucs4); + if (glyph != 0) { + glyphs->glyphs[glyph_pos] = glyph; + if (!(flags & GlyphIndicesOnly)) { + QGlyphLayout g = glyphs->mid(glyph_pos, 1); + engine->recalcAdvances(&g, flags); + } + + // set the high byte to indicate which engine the glyph came from + glyphs->glyphs[glyph_pos] |= (lastFallback << 24); + } else { + lastFallback = -1; + } + } else { + lastFallback = -1; + } + + if (glyphs->glyphs[glyph_pos] == 0 + && ucs4 != QChar::LineSeparator + && ucs4 != QChar::LineFeed + && ucs4 != QChar::CarriageReturn + && ucs4 != QChar::ParagraphSeparator) { + if (!m_fallbackFamiliesQueried) + const_cast(this)->ensureFallbackFamiliesQueried(); + for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) { + QFontEngine *engine = m_engines.at(x); + if (!engine) { + if (!shouldLoadFontEngineForCharacter(x, ucs4)) + continue; + const_cast(this)->ensureEngineAt(x); + engine = m_engines.at(x); + if (!engine) + continue; + } + Q_ASSERT(engine != nullptr); + if (engine->type() == Box) + continue; + + glyph_t glyph = engine->glyphIndex(ucs4); + if (glyph != 0) { + glyphs->glyphs[glyph_pos] = glyph; + if (!(flags & GlyphIndicesOnly)) { + QGlyphLayout g = glyphs->mid(glyph_pos, 1); + engine->recalcAdvances(&g, flags); + } + + lastFallback = x; + + // set the high byte to indicate which engine the glyph came from + glyphs->glyphs[glyph_pos] |= (x << 24); + break; + } + } + } + + it.advance(); + ++glyph_pos; + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + + return true; +} + +bool QFontEngineMulti::shouldLoadFontEngineForCharacter(int at, uint ucs4) const +{ + Q_UNUSED(at); + Q_UNUSED(ucs4); + return true; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs <= 0) + return glyph_metrics_t(); + + glyph_metrics_t overall; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + return overall; +} + +void QFontEngineMulti::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + int which = highByte(glyph); + ensureEngineAt(which); + engine(which)->getGlyphBearings(stripped(glyph), leftBearing, rightBearing); +} + +void QFontEngineMulti::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (glyphs.numGlyphs <= 0) + return; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + if (flags & QTextItem::RightToLeft) { + for (int gl = 0; gl < glyphs.numGlyphs; gl++) + x += glyphs.advances[gl].toReal(); + } + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) + x -= glyphs.advances[i].toReal(); + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + if (!(flags & QTextItem::RightToLeft)) { + for (i = start; i < end; ++i) + x += glyphs.advances[i].toReal(); + } + + // change engine + start = end; + which = e; + } + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) + x -= glyphs.advances[i].toReal(); + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; +} + +void QFontEngineMulti::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +void QFontEngineMulti::doKerning(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(glyph_t glyph) +{ + const int which = highByte(glyph); + return engine(which)->boundingBox(stripped(glyph)); +} + +QFixed QFontEngineMulti::ascent() const +{ return engine(0)->ascent(); } + +QFixed QFontEngineMulti::capHeight() const +{ return engine(0)->capHeight(); } + +QFixed QFontEngineMulti::descent() const +{ return engine(0)->descent(); } + +QFixed QFontEngineMulti::leading() const +{ + return engine(0)->leading(); +} + +QFixed QFontEngineMulti::xHeight() const +{ + return engine(0)->xHeight(); +} + +QFixed QFontEngineMulti::averageCharWidth() const +{ + return engine(0)->averageCharWidth(); +} + +QFixed QFontEngineMulti::lineThickness() const +{ + return engine(0)->lineThickness(); +} + +QFixed QFontEngineMulti::underlinePosition() const +{ + return engine(0)->underlinePosition(); +} + +qreal QFontEngineMulti::maxCharWidth() const +{ + return engine(0)->maxCharWidth(); +} + +qreal QFontEngineMulti::minLeftBearing() const +{ + return engine(0)->minLeftBearing(); +} + +qreal QFontEngineMulti::minRightBearing() const +{ + return engine(0)->minRightBearing(); +} + +bool QFontEngineMulti::canRender(const QChar *string, int len) const +{ + if (engine(0)->canRender(string, len)) + return true; + + int nglyphs = len; + + QVarLengthArray glyphs(nglyphs); + + QGlyphLayout g; + g.numGlyphs = nglyphs; + g.glyphs = glyphs.data(); + if (!stringToCMap(string, len, &g, &nglyphs, GlyphIndicesOnly)) + Q_UNREACHABLE(); + + for (int i = 0; i < nglyphs; i++) { + if (glyphs[i] == 0) + return false; + } + + return true; +} + +/* Implement alphaMapForGlyph() which is called by QPA Windows code. + * Ideally, that code should be fixed to correctly handle QFontEngineMulti. */ + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t glyph) +{ + const int which = highByte(glyph); + return engine(which)->alphaMapForGlyph(stripped(glyph)); +} + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) +{ + const int which = highByte(glyph); + return engine(which)->alphaMapForGlyph(stripped(glyph), subPixelPosition); +} + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t glyph, const QTransform &t) +{ + const int which = highByte(glyph); + return engine(which)->alphaMapForGlyph(stripped(glyph), t); +} + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) +{ + const int which = highByte(glyph); + return engine(which)->alphaMapForGlyph(stripped(glyph), subPixelPosition, t); +} + +QImage QFontEngineMulti::alphaRGBMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) +{ + const int which = highByte(glyph); + return engine(which)->alphaRGBMapForGlyph(stripped(glyph), subPixelPosition, t); +} + +/* + This is used indirectly by Qt WebKit when using QTextLayout::setRawFont + + The purpose of this is to provide the necessary font fallbacks when drawing complex + text. Since Qt WebKit ends up repeatedly creating QTextLayout instances and passing them + the same raw font over and over again, we want to cache the corresponding multi font engine + as it may contain fallback font engines already. +*/ +QFontEngine *QFontEngineMulti::createMultiFontEngine(QFontEngine *fe, int script) +{ + QFontEngine *engine = nullptr; + QFontCache::Key key(fe->fontDef, script, /*multi = */true); + QFontCache *fc = QFontCache::instance(); + // We can't rely on the fontDef (and hence the cache Key) + // alone to distinguish webfonts, since these should not be + // accidentally shared, even if the resulting fontcache key + // is strictly identical. See: + // http://www.w3.org/TR/css3-fonts/#font-face-rule + const bool faceIsLocal = !fe->faceId().filename.isEmpty(); + QFontCache::EngineCache::Iterator it = fc->engineCache.find(key), + end = fc->engineCache.end(); + while (it != end && it.key() == key) { + Q_ASSERT(it.value().data->type() == QFontEngine::Multi); + QFontEngineMulti *cachedEngine = static_cast(it.value().data); + if (fe == cachedEngine->engine(0) || (faceIsLocal && fe->faceId().filename == cachedEngine->engine(0)->faceId().filename)) { + engine = cachedEngine; + fc->updateHitCountAndTimeStamp(it.value()); + break; + } + ++it; + } + if (!engine) { + engine = QGuiApplicationPrivate::instance()->platformIntegration()->fontDatabase()->fontEngineMulti(fe, QChar::Script(script)); + fc->insertEngine(key, engine, /* insertMulti */ !faceIsLocal); + } + Q_ASSERT(engine); + return engine; +} + +QTestFontEngine::QTestFontEngine(int size) + : QFontEngineBox(TestFontEngine, size) +{} + +QT_END_NAMESPACE