diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index f495c9b8d..27fbf3c6d 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -178,6 +178,88 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
return true;
}
+QString buildStyle(const QJsonObject& obj)
+{
+ QStringList styles;
+ if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) {
+ styles << QString("color: %1;").arg(color);
+ }
+ if (obj.contains("bold")) {
+ QString weight = "normal";
+ if (Json::ensureBoolean(obj, "bold", false)) {
+ weight = "bold";
+ }
+ styles << QString("font-weight: %1;").arg(weight);
+ }
+ if (obj.contains("italic")) {
+ QString style = "normal";
+ if (Json::ensureBoolean(obj, "italic", false)) {
+ style = "italic";
+ }
+ styles << QString("font-style: %1;").arg(style);
+ }
+
+ return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" "));
+}
+
+QString processComponent(const QJsonArray& value, bool strikethrough, bool underline)
+{
+ QString result;
+ for (auto current : value)
+ result += processComponent(current, strikethrough, underline);
+ return result;
+}
+
+QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline)
+{
+ underline = Json::ensureBoolean(obj, "underlined", underline);
+ strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough);
+
+ QString result = Json::ensureString(obj, "text");
+ if (underline) {
+ result = QString("%1").arg(result);
+ }
+ if (strikethrough) {
+ result = QString("%1").arg(result);
+ }
+ // the extra needs to be a array
+ result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline);
+ if (auto style = buildStyle(obj); !style.isEmpty()) {
+ result = QString("%2").arg(style, result);
+ }
+ if (obj.contains("clickEvent")) {
+ auto click_event = Json::ensureObject(obj, "clickEvent");
+ auto action = Json::ensureString(click_event, "action");
+ auto value = Json::ensureString(click_event, "value");
+ if (action == "open_url" && !value.isEmpty()) {
+ result = QString("%2").arg(value, result);
+ }
+ }
+ return result;
+}
+
+QString processComponent(const QJsonValue& value, bool strikethrough, bool underline)
+{
+ if (value.isString()) {
+ return value.toString();
+ }
+ if (value.isBool()) {
+ return value.toBool() ? "true" : "false";
+ }
+ if (value.isDouble()) {
+ return QString::number(value.toDouble());
+ }
+ if (value.isArray()) {
+ return processComponent(value.toArray(), strikethrough, underline);
+ }
+ if (value.isObject()) {
+ return processComponent(value.toObject(), strikethrough, underline);
+ }
+ qWarning() << "Invalid component type!";
+ return {};
+}
+
+// https://minecraft.wiki/w/Raw_JSON_text_format
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
@@ -186,7 +268,9 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
- pack.setDescription(Json::ensureString(pack_obj, "description", ""));
+
+ pack.setDescription(processComponent(pack_obj.value("description")));
+
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
return false;
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
index 5199bf3f0..97bf7b2ba 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
@@ -34,6 +34,7 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
+QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp
index 69f72fea2..44f702659 100644
--- a/launcher/ui/widgets/InfoFrame.cpp
+++ b/launcher/ui/widgets/InfoFrame.cpp
@@ -36,6 +36,8 @@
#include
#include
+#include
+#include
#include
#include "InfoFrame.h"
@@ -274,12 +276,27 @@ void InfoFrame::setDescription(QString text)
}
QString labeltext;
labeltext.reserve(300);
- if (finaltext.length() > 290) {
+
+ // elide rich text by getting characters without formatting
+ const int maxCharacterElide = 290;
+ QTextDocument doc;
+ doc.setHtml(text);
+
+ if (doc.characterCount() > maxCharacterElide) {
ui->descriptionLabel->setOpenExternalLinks(false);
- ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText);
+ ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here.
m_description = text;
- // This allows injecting HTML here.
- labeltext.append("" + finaltext.left(287) + "...");
+
+ // move the cursor to the character elide, doesn't see html
+ QTextCursor cursor(&doc);
+ cursor.movePosition(QTextCursor::End);
+ cursor.setPosition(maxCharacterElide, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+
+ // insert the post fix at the cursor
+ cursor.insertHtml("...");
+
+ labeltext.append(doc.toHtml());
QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler);
} else {
ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText);
@@ -316,7 +333,7 @@ void InfoFrame::setLicense(QString text)
if (finaltext.length() > 290) {
ui->licenseLabel->setOpenExternalLinks(false);
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
- m_description = text;
+ m_license = text;
// This allows injecting HTML here.
labeltext.append("" + finaltext.left(287) + "...");
QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 59e0e3144..2dedb47cc 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -57,5 +57,8 @@ ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}:
ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Version)
+ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME MetaComponentParse)
+
ecm_add_test(CatPack_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME CatPack)
diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp
new file mode 100644
index 000000000..9979a9fa6
--- /dev/null
+++ b/tests/MetaComponentParse_test.cpp
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+class MetaComponentParseTest : public QObject {
+ Q_OBJECT
+
+ void doTest(QString name)
+ {
+ QString source = QFINDTESTDATA("testdata/MetaComponentParse");
+
+ QString comp_rp = FS::PathCombine(source, name);
+
+ QFile file;
+ file.setFileName(comp_rp);
+ QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text));
+ QString data = file.readAll();
+ file.close();
+
+ QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
+ QJsonObject obj = doc.object();
+
+ QJsonValue description_json = obj.value("description");
+ QJsonValue expected_json = obj.value("expected_output");
+
+ QVERIFY(!description_json.isUndefined());
+ QVERIFY(expected_json.isString());
+
+ QString expected = expected_json.toString();
+
+ QString processed = ResourcePackUtils::processComponent(description_json);
+
+ QCOMPARE(processed, expected);
+ }
+
+ private slots:
+ void test_parseComponentBasic() { doTest("component_basic.json"); }
+ void test_parseComponentWithFormat() { doTest("component_with_format.json"); }
+ void test_parseComponentWithExtra() { doTest("component_with_extra.json"); }
+ void test_parseComponentWithLink() { doTest("component_with_link.json"); }
+ void test_parseComponentWithMixed() { doTest("component_with_mixed.json"); }
+};
+
+QTEST_GUILESS_MAIN(MetaComponentParseTest)
+
+#include "MetaComponentParse_test.moc"
diff --git a/tests/testdata/MetaComponentParse/component_basic.json b/tests/testdata/MetaComponentParse/component_basic.json
new file mode 100644
index 000000000..908cb353c
--- /dev/null
+++ b/tests/testdata/MetaComponentParse/component_basic.json
@@ -0,0 +1,8 @@
+{
+ "description": [
+ {
+ "text": "Hello, Component!"
+ }
+ ],
+ "expected_output": "Hello, Component!"
+}
diff --git a/tests/testdata/MetaComponentParse/component_with_extra.json b/tests/testdata/MetaComponentParse/component_with_extra.json
new file mode 100644
index 000000000..887becdbe
--- /dev/null
+++ b/tests/testdata/MetaComponentParse/component_with_extra.json
@@ -0,0 +1,21 @@
+{
+ "description": [
+ {
+ "text": "Hello, ",
+ "color": "red",
+ "bold": true,
+ "italic": true,
+ "extra": [
+ {
+ "extra": [
+ "Component!"
+ ],
+ "bold": false,
+ "italic": false
+ }
+ ]
+ }
+ ],
+ "expected_output":
+ "Hello, Component!"
+}
\ No newline at end of file
diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json
new file mode 100644
index 000000000..1078886a6
--- /dev/null
+++ b/tests/testdata/MetaComponentParse/component_with_format.json
@@ -0,0 +1,13 @@
+{
+ "description": [
+ {
+ "text": "Hello, Component!",
+ "color": "blue",
+ "bold": true,
+ "italic": true,
+ "underlined": true,
+ "strikethrough": true
+ }
+ ],
+ "expected_output": "Hello, Component!"
+}
\ No newline at end of file
diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json
new file mode 100644
index 000000000..188c004cd
--- /dev/null
+++ b/tests/testdata/MetaComponentParse/component_with_link.json
@@ -0,0 +1,12 @@
+{
+ "description": [
+ {
+ "text": "Hello, Component!",
+ "clickEvent": {
+ "action": "open_url",
+ "value": "https://google.com"
+ }
+ }
+ ],
+ "expected_output": "Hello, Component!"
+}
diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json
new file mode 100644
index 000000000..661fc1a3e
--- /dev/null
+++ b/tests/testdata/MetaComponentParse/component_with_mixed.json
@@ -0,0 +1,45 @@
+{
+ "description": [
+ {
+ "text": "The quick ",
+ "color": "blue",
+ "italic": true
+ },
+ {
+ "text": "brown fox ",
+ "color": "#873600",
+ "bold": true,
+ "underlined": true,
+ "extra": [
+ {
+ "text": "jumped over ",
+ "color": "blue",
+ "bold": false,
+ "underlined": false,
+ "italic": true,
+ "strikethrough": true
+ }
+ ]
+ },
+ {
+ "text": "the lazy dog's back. ",
+ "color": "green",
+ "bold": true,
+ "italic": true,
+ "underlined": true,
+ "strikethrough": true,
+ "extra": [
+ {
+ "text": "1234567890 ",
+ "color": "black",
+ "strikethrough": false,
+ "extra": [
+ "How vexingly quick daft zebras jump!"
+ ]
+ }
+ ]
+ }
+ ],
+ "expected_output":
+ "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!"
+}