// 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
// Qt-Security score:critical reason:data-parser
#include <qdebug.h>
#include <private/qfontengine_p.h>
#include <private/qfontengineglyphcache_p.h>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformfontdatabase.h>
#include <qpa/qplatformintegration.h>
#include "qbitmap.h"
#include "qpainter.h"
#include "qpainterpath.h"
#include "qvarlengtharray.h"
#include "qtextengine_p.h"
#include <qmath.h>
#include <qendian.h>
#include <private/qstringiterator_p.h>
#if QT_CONFIG(harfbuzz)
# include "qharfbuzzng_p.h"
# include <hb-ot.h>
#endif
#include <algorithm>
#include <limits.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcColrv1, "qt.text.font.colrv1")
using namespace Qt::StringLiterals;
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<typename T>
static inline bool qSafeFromBigEndian(const uchar *source, const uchar *end, T *output)
{
if (source + sizeof(T) > end)
return false;
*output = qFromBigEndian<T>(source);
return true;
}
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 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<QFontEngine *> *enginesCollector = nullptr;
Q_AUTOTEST_EXPORT void QFontEngine_startCollectingEngines()
{
delete enginesCollector;
enginesCollector = new QList<QFontEngine *>();
}
Q_AUTOTEST_EXPORT QList<QFontEngine *> QFontEngine_stopCollectingEngines()
{
Q_ASSERT(enginesCollector);
QList<QFontEngine *> ret = *enginesCollector;
delete enginesCollector;
enginesCollector = nullptr;
return ret;
}
#endif // QT_BUILD_INTERNAL
// QFontEngine
#define kBearingNotInitialized std::numeric_limits<qreal>::max()
QFontEngine::QFontEngine(Type type)
: m_type(type), ref(0),
font_(),
face_(),
m_heightMetricsQueried(false),
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 / 10;
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)
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)
return hb_qt_face_get_for_engine(const_cast<QFontEngine *>(this));
#else
return nullptr;
#endif
}
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)
// 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];
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;
}
#endif
return false;
}
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<QFontEngine *>(this)->boundingBox(glyph);
return bb.height;
}
QFixed QFontEngine::xHeight() const
{
const glyph_t glyph = glyphIndex('x');
glyph_metrics_t bb = const_cast<QFontEngine *>(this)->boundingBox(glyph);
return bb.height;
}
QFixed QFontEngine::averageCharWidth() const
{
const glyph_t glyph = glyphIndex('x');
glyph_metrics_t bb = const_cast<QFontEngine *>(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<glyph_t> &glyphs_out, QVarLengthArray<QFixedPoint> &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 = u'\x640'; // Kashida character
glyph_t kashidaGlyph = glyphIndex(ch.unicode());
QFixed kashidaWidth;
QGlyphLayout g;
g.numGlyphs = 1;
g.glyphs = &kashidaGlyph;
g.advances = &kashidaWidth;
recalcAdvances(&g, { });
|