Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qlottietextlayer_p.h"
#include <QJsonArray>
#include <QJsonObject>
#include "qlottieconstants_p.h"
#include "qlottierenderer_p.h"
QT_BEGIN_NAMESPACE
using namespace Qt::Literals::StringLiterals;
namespace {
QPainterPath buildContour(const QJsonObject &shape)
{
const QJsonArray bezierIn = shape.value("i"_L1).toArray();
const QJsonArray bezierOut = shape.value("o"_L1).toArray();
const QJsonArray vertices = shape.value("v"_L1).toArray();
const bool closed = shape.value("c"_L1).toBool();
QPainterPath path;
if (vertices.count() < 2)
return path;
auto toPoint = [](const QJsonArray &arr, int idx) {
return QPointF(arr.at(idx).toArray().at(0).toDouble(),
arr.at(idx).toArray().at(1).toDouble());
};
QPointF s = toPoint(vertices, 0);
path.moveTo(s);
for (int i = 0; i < vertices.count() - 1; ++i) {
QPointF v = toPoint(vertices, i + 1);
QPointF c1 = s + toPoint(bezierOut, i);
QPointF c2 = v + toPoint(bezierIn, i + 1);
path.cubicTo(c1, c2, v);
s = v;
}
if (closed) {
QPointF start = toPoint(vertices, 0);
QPointF c1 = s + toPoint(bezierOut, vertices.count() - 1);
QPointF c2 = start + toPoint(bezierIn, 0);
path.cubicTo(c1, c2, start);
path.closeSubpath();
}
return path;
}
QPainterPath buildGlyphPathFromData(const QJsonObject &data)
{
QPainterPath path;
path.setFillRule(Qt::WindingFill);
for (const QJsonValue &shapeVal : data.value("shapes"_L1).toArray()) {
for (const QJsonValue &itemVal : shapeVal.toObject().value("it"_L1).toArray()) {
const QJsonObject item = itemVal.toObject();
if (item.value("ty"_L1).toString() == "sh"_L1) {
const QJsonObject ks = item.value("ks"_L1).toObject();
if (!ks.value("a"_L1).toInt()) // static (not animated)
path.addPath(buildContour(ks.value("k"_L1).toObject()));
}
}
}
return path;
}
QString charKey(const QChar &ch, const QString &fFamily)
{
return QString(ch) + u'|' + fFamily;
}
} // namespace
QLottieTextLayer::QLottieTextLayer(const QLottieTextLayer &other)
: QLottieLayer(other)
{
m_textDocument = other.m_textDocument;
m_chars = other.m_chars;
}
QLottieBase *QLottieTextLayer::clone() const
{
return new QLottieTextLayer(*this);
}
void QLottieTextLayer::render(QLottieRenderer &renderer) const
{
if (!m_isActive)
return;
renderer.saveState();
applyLayerTransform(renderer);
renderer.render(*this);
renderer.finish(*this);
renderer.restoreState();
}
int QLottieTextLayer::parse(const QJsonObject &definition)
{
m_type = LOTTIE_LAYER_TEXT_IX;
int ret = QLottieLayer::parse(definition);
if (m_hidden)
return 0;
const QJsonArray keyframes = definition.value("t"_L1).toObject()
.value("d"_L1).toObject()
.value("k"_L1).toArray();
if (!keyframes.isEmpty()) {
const QJsonObject doc = keyframes.first().toObject().value("s"_L1).toObject();
m_textDocument.text = doc.value("t"_L1).toString();
m_textDocument.fontFamily = doc.value("f"_L1).toString();
m_textDocument.fontSize = doc.value("s"_L1).toDouble(12.0);
m_textDocument.justification = doc.value("j"_L1).toInt(0);
m_textDocument.tracking = doc.value("tr"_L1).toDouble(0.0);
const QJsonArray fc = doc.value("fc"_L1).toArray();
if (fc.size() >= 3) {
m_textDocument.fillColor = QColor::fromRgbF(
fc.at(0).toDouble(),
fc.at(1).toDouble(),
fc.at(2).toDouble(),
fc.size() >= 4 ? fc.at(3).toDouble() : 1.0);
}
}
qCDebug(lcLottieQtLottieParser) << "QLottieTextLayer::parse()" << m_name;
return ret;
}
void QLottieTextLayer::setCharData(const QMap<QString, QJsonObject> &chars)
{
m_chars.clear();
for (auto it = chars.constBegin(); it != chars.constEnd(); ++it) {
CharInfo info;
info.advanceWidth = it.value().value("w"_L1).toDouble();
info.glyphPath = buildGlyphPathFromData(it.value().value("data"_L1).toObject());
m_chars.insert(it.key(), info);
}
}
QList<QPainterPath> QLottieTextLayer::buildGlyphPaths() const
{
if (m_textDocument.text.isEmpty() || m_chars.isEmpty())
return {};
const qreal scale = m_textDocument.fontSize / 100.0;
const qreal trackingPx = m_textDocument.tracking * scale / 10.0;
qreal totalWidth = 0;
for (const QChar &ch : m_textDocument.text) {
auto it = m_chars.constFind(charKey(ch, m_textDocument.fontFamily));
if (it != m_chars.constEnd())
totalWidth += it->advanceWidth * scale + trackingPx;
}
qreal x = 0;
if (m_textDocument.justification == 1) // right align
x = -totalWidth;
else if (m_textDocument.justification == 2) // center align
x = -totalWidth / 2.0;
QList<QPainterPath> result;
for (const QChar &ch : m_textDocument.text) {
auto it = m_chars.constFind(charKey(ch, m_textDocument.fontFamily));
if (it != m_chars.constEnd()) {
if (!it->glyphPath.isEmpty()) {
QTransform t;
t.translate(x, 0);
t.scale(scale, scale);
result.append(t.map(it->glyphPath));
}
x += it->advanceWidth * scale + trackingPx;
}
}
return result;
}
QT_END_NAMESPACE