/* SPDX-FileCopyrightText: 2024 Vlad Zahorodnii SPDX-License-Identifier: GPL-2.0-or-later */ #include "utils/svgcursorreader.h" #include "utils/common.h" #include #include #include #include #include #include #include namespace KWin { struct SvgCursorMetaDataEntry { static std::optional parse(const QJsonObject &object); QString fileName; qreal nominalSize; QPointF hotspot; std::chrono::milliseconds delay; }; std::optional SvgCursorMetaDataEntry::parse(const QJsonObject &object) { const QJsonValue fileName = object.value(QLatin1String("filename")); if (!fileName.isString()) { return std::nullopt; } const QJsonValue nominalSize = object.value(QLatin1String("nominal_size")); if (!nominalSize.isDouble()) { return std::nullopt; } const QJsonValue hotspotX = object.value(QLatin1String("hotspot_x")); if (!hotspotX.isDouble()) { return std::nullopt; } const QJsonValue hotspotY = object.value(QLatin1String("hotspot_y")); if (!hotspotY.isDouble()) { return std::nullopt; } const QJsonValue delay = object.value(QLatin1String("delay")); return SvgCursorMetaDataEntry{ .fileName = fileName.toString(), .nominalSize = nominalSize.toDouble(), .hotspot = QPointF(hotspotX.toDouble(), hotspotY.toDouble()), .delay = std::chrono::milliseconds(delay.toInt()), }; } struct SvgCursorMetaData { static std::optional parse(const QString &filePath); QList entries; }; std::optional SvgCursorMetaData::parse(const QString &filePath) { QFile metaDataFile(filePath); if (!metaDataFile.open(QIODevice::ReadOnly | QIODevice::Text)) { return std::nullopt; } QJsonParseError jsonParseError; const QJsonDocument document = QJsonDocument::fromJson(metaDataFile.readAll(), &jsonParseError); if (jsonParseError.error) { return std::nullopt; } if (!document.isArray()) { return std::nullopt; } const QJsonArray array = document.array(); if (array.isEmpty()) { return std::nullopt; } QList entries; for (int i = 0; i < array.size(); ++i) { const QJsonValue element = array.at(i); if (!element.isObject()) { return std::nullopt; } if (const auto entry = SvgCursorMetaDataEntry::parse(element.toObject())) { entries.append(entry.value()); } else { return std::nullopt; } } return SvgCursorMetaData{ .entries = entries, }; } QList SvgCursorReader::load(const QString &containerPath, int desiredSize, qreal devicePixelRatio) { const QDir containerDir(containerPath); const QString metadataFilePath = containerDir.filePath(QStringLiteral("metadata.json")); const auto metadata = SvgCursorMetaData::parse(metadataFilePath); if (!metadata.has_value()) { qCWarning(KWIN_CORE) << "Failed to parse" << metadataFilePath; return {}; } QList sprites; for (const SvgCursorMetaDataEntry &entry : metadata->entries) { const QString filePath = containerDir.filePath(entry.fileName); const qreal scale = desiredSize / entry.nominalSize; QSvgRenderer renderer(filePath); if (!renderer.isValid()) { qCWarning(KWIN_CORE) << "Failed to render" << filePath; return {}; } const QRect bounds(QPoint(0, 0), renderer.defaultSize() * scale); QImage image(bounds.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); image.setDevicePixelRatio(devicePixelRatio); QPainter painter(&image); renderer.render(&painter, bounds); painter.end(); sprites.append(CursorSprite(image, entry.hotspot * scale, entry.delay)); } return sprites; } } // namespace KWin