[Fonts] Retire max font size setting into theme CardText min/max range

This commit is contained in:
Lukas Brübach 2026-10-16 11:27:31 +02:00
parent 90665e6e8b
commit 748f9fb40d
19 changed files with 486 additions and 53 deletions

View file

@ -137,6 +137,9 @@ SettingsCache::SettingsCache()
// before that rename (idempotent, safe to run on every startup, portable builds included).
SettingsMigration::migrateGameFiltersToCamelCase(settingsPath);
// Retire appearance/maxFontSize into per-theme CardText max (idempotent, safe every startup).
SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath);
shortcutsSettings = new ShortcutsSettings(settingsPath, this);
cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this);
serversSettings = new ServersSettings(settingsPath, this);

View file

@ -36,7 +36,6 @@
#include <QtGlobal>
#include <algorithm>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/settings/appearance_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/debug_settings.h>
#include <qnamespace.h>
@ -136,8 +135,8 @@ QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{
const int MAX_FONT_SIZE = SettingsCache::instance().appearance().getMaxFontSize();
const int fontSize = std::max(9, MAX_FONT_SIZE);
const int minSize = std::max(1, themeManager->cardTextMinSize());
const int fontSize = std::max(minSize, std::max(1, themeManager->cardTextMaxSize()));
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());

View file

@ -13,6 +13,7 @@
static constexpr int MIN_FONT_SIZE = 6;
static constexpr int MAX_FONT_SIZE = 72;
static constexpr int CARD_TEXT_MIN_SIZE = 1;
static constexpr int FALLBACK_FONT_SIZE = 10;
static const QMap<AppFont::Role, const char *> APP_FONT_DESCRIPTIONS = {
@ -46,6 +47,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
grid->setColumnStretch(0, 0);
grid->setColumnStretch(1, 1);
grid->setColumnStretch(2, 0);
grid->setColumnStretch(3, 0);
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
@ -58,6 +60,23 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
grid->addWidget(familyCombo, i, 1);
connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); });
if (role == AppFont::CardText) {
auto *minSpin = new QSpinBox(this);
minSpin->setRange(CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE);
grid->addWidget(minSpin, i, 2);
connect(minSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
auto *maxSpin = new QSpinBox(this);
maxSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
grid->addWidget(maxSpin, i, 3);
connect(maxSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
label->setBuddy(familyCombo);
fontRows[role] = {label, familyCombo, maxSpin, minSpin};
continue;
}
auto *sizeSpin = new QSpinBox(this);
sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
grid->addWidget(sizeSpin, i, 2);
@ -65,7 +84,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
label->setBuddy(familyCombo);
fontRows[role] = {label, familyCombo, sizeSpin};
fontRows[role] = {label, familyCombo, sizeSpin, nullptr};
}
retranslateUi();
@ -94,6 +113,11 @@ void FontGridWidget::loadFonts(const FontConfig &cfg)
size = FALLBACK_FONT_SIZE;
}
row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE));
if (role == AppFont::CardText && row.minSpin != nullptr) {
int minSize = cfg.cardTextMinSize > 0 ? cfg.cardTextMinSize : themeManager->cardTextMinSize();
row.minSpin->setValue(qBound(CARD_TEXT_MIN_SIZE, minSize, MAX_FONT_SIZE));
}
}
blockSignals(false);
}
@ -111,6 +135,10 @@ FontConfig FontGridWidget::currentFontConfig() const
QFont font(row.familyCombo->currentFont().family());
font.setPointSize(row.sizeSpin->value());
cfg.fonts[role] = font;
if (role == AppFont::CardText && row.minSpin != nullptr) {
cfg.cardTextMinSize = row.minSpin->value();
}
}
return cfg;
}
@ -126,6 +154,15 @@ void FontGridWidget::retranslateUi()
row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
row.familyCombo->setAccessibleName(roleDisplayName(role));
if (role == AppFont::CardText && row.minSpin != nullptr) {
row.minSpin->setToolTip(tr("Minimum card text size"));
row.minSpin->setAccessibleName(tr("Minimum card text size"));
row.minSpin->setSuffix(tr(" pt"));
row.sizeSpin->setToolTip(tr("Maximum card text size"));
row.sizeSpin->setAccessibleName(tr("Maximum card text size"));
row.sizeSpin->setSuffix(tr(" pt"));
continue;
}
row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role)));
row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role)));
row.sizeSpin->setSuffix(tr(" pt"));

View file

@ -35,6 +35,7 @@ private:
QLabel *label = nullptr;
QFontComboBox *familyCombo = nullptr;
QSpinBox *sizeSpin = nullptr;
QSpinBox *minSpin = nullptr; // CardText only: shrink-to-fit floor
};
QMap<AppFont::Role, FontRow> fontRows;

View file

@ -308,7 +308,7 @@ QPalette PaletteConfig::apply(QPalette base) const
bool FontConfig::hasFonts() const
{
return !fonts.isEmpty();
return !fonts.isEmpty() || cardTextMinSize != 1;
}
QString FontConfig::toToml() const
@ -334,6 +334,10 @@ QString FontConfig::toToml() const
entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize);
}
if (cardTextMinSize != 1) {
entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMinSize"), -20).arg(cardTextMinSize);
}
if (entries.isEmpty()) {
return {};
}
@ -401,6 +405,22 @@ FontConfig FontConfig::fromFile(const QString &filePath)
value = value.mid(1, value.size() - 2).trimmed();
}
// CardText floor: bare integer, e.g. CardTextMinSize = 2. Tolerate
// quoting like the family entries; skip invalid lines.
QString minKey = key;
if (minKey.startsWith("AppFont::")) {
minKey = minKey.mid(QStringLiteral("AppFont::").size());
}
if (minKey.compare("CardTextMinSize", Qt::CaseInsensitive) == 0) {
bool ok = false;
int minSize = value.trimmed().toInt(&ok);
if (!ok || minSize <= 0) {
continue;
}
cfg.cardTextMinSize = minSize;
continue;
}
int comma = value.lastIndexOf(',');
if (comma < 0) {

View file

@ -84,15 +84,20 @@ struct PaletteConfig
* @code
* [Fonts]
* InterfaceDefault = "Inter, 10"
* CardText = "Noto Serif, 12"
* CardTextMinSize = 1
* @endcode
* CardText stores family + MAX size; cardTextMinSize stores the overlay
* shrink-to-fit floor (omitted from TOML when 1).
*/
struct FontConfig
{
QMap<AppFont::Role, QFont> fonts;
int cardTextMinSize = 1;
bool operator==(const FontConfig &rhs) const
{
return fonts == rhs.fonts;
return fonts == rhs.fonts && cardTextMinSize == rhs.cardTextMinSize;
}
bool operator!=(const FontConfig &rhs) const
{

View file

@ -673,7 +673,12 @@ QFont ThemeManager::appFont(AppFont::Role role) const
}
return monoFont;
}
if (role == AppFont::CardText || role == AppFont::DeckList) {
if (role == AppFont::CardText) {
QFont serifFont(QStringLiteral("Serif"));
serifFont.setPointSize(12);
return serifFont;
}
if (role == AppFont::DeckList) {
return QFont(QStringLiteral("Serif"));
}
@ -682,6 +687,26 @@ QFont ThemeManager::appFont(AppFont::Role role) const
return qApp->font();
}
int ThemeManager::cardTextMaxSize() const
{
const auto it = currentFonts.fonts.constFind(AppFont::CardText);
if (it != currentFonts.fonts.constEnd()) {
int size = it.value().pointSize();
if (size > 0) {
return size;
}
}
return 12;
}
int ThemeManager::cardTextMinSize() const
{
if (currentFonts.cardTextMinSize > 0) {
return currentFonts.cardTextMinSize;
}
return 1;
}
void ThemeManager::themeChangedSlot()
{
QString themeName = SettingsCache::instance().getThemeName();

View file

@ -154,6 +154,10 @@ public:
* ChatLog, serif basis for CardText and DeckList, the application
* font for the remaining roles). */
QFont appFont(AppFont::Role role) const;
/** @brief CardText max size: stored CardText point size, else 12. */
int cardTextMaxSize() const;
/** @brief CardText shrink-to-fit floor: stored CardTextMinSize, else 1. */
int cardTextMinSize() const;
QBrush &getBgBrush(Role zone);
QBrush getExtraBgBrush(Role zone, int zoneId = 0);

View file

@ -87,6 +87,7 @@ void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color)
void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size)
{
fontSize = size > 0 ? size : 1;
hasExplicitFontSize = true;
update();
}
@ -179,10 +180,12 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
}
// Calculate the optimal font size. The family comes from the CardText
// theme role; the fontSize member stays the starting size and the
// shrink-to-fit logic below is unchanged.
// theme role; the start size is the explicit size when set (e.g. deck
// preview 24), else the theme max, shrinking down to the theme min floor.
QFont font = themeManager->appFont(AppFont::CardText);
int optimalFontSize = fontSize; // Start with the user-defined font size
const int themeMin = std::max(1, themeManager->cardTextMinSize());
const int themeMax = std::max(themeMin, std::max(1, themeManager->cardTextMaxSize()));
int optimalFontSize = hasExplicitFontSize ? std::max(1, fontSize) : themeMax;
const QFontMetrics baseMetrics(font);
int textWidth = pixmapRect.width();
@ -200,7 +203,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
}
--optimalFontSize;
} while (optimalFontSize > 1);
} while (optimalFontSize > themeMin);
// Apply the calculated font size
painter.setFont(font);
@ -227,7 +230,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height();
// Adjust font size if the total text height exceeds the pixmap height
while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) {
while (totalTextHeight > pixmapRect.height() && optimalFontSize > themeMin) {
--optimalFontSize;
font.setPointSize(optimalFontSize);
painter.setFont(font);
@ -297,7 +300,9 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
// Get the font metrics for the overlay text
QFont font = themeManager->appFont(AppFont::CardText);
font.setPointSize(fontSize);
const int themeMin = std::max(1, themeManager->cardTextMinSize());
const int themeMax = std::max(themeMin, std::max(1, themeManager->cardTextMaxSize()));
font.setPointSize(hasExplicitFontSize ? std::max(1, fontSize) : themeMax);
const QFontMetrics fontMetrics(font);
// Calculate the height required for the text

View file

@ -60,6 +60,7 @@ private:
QColor textColor;
QColor outlineColor;
int fontSize;
bool hasExplicitFontSize = false;
Qt::Alignment textAlignment;
bool highlighted;
};

View file

@ -284,20 +284,11 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(),
&CardsDisplaySettings::setRoundCardCorners);
connect(&maxFontSizeForCardsEdit, qOverload<int>(&QSpinBox::valueChanged), &settings.appearance(),
&AppearanceSettings::setMaxFontSize);
maxFontSizeForCardsEdit.setValue(settings.appearance().getMaxFontSize());
maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit);
maxFontSizeForCardsEdit.setMinimum(9);
maxFontSizeForCardsEdit.setMaximum(100);
auto *cardsGrid = new QGridLayout;
cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2);
cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2);
cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2);
cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2);
cardsGrid->addWidget(&maxFontSizeForCardsLabel, 4, 0, 1, 1);
cardsGrid->addWidget(&maxFontSizeForCardsEdit, 4, 1, 1, 1);
cardsGroupBox = new QGroupBox;
cardsGroupBox->setLayout(cardsGrid);
@ -658,7 +649,6 @@ void AppearanceSettingsPage::retranslateUi()
autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout"));
cardScalingCheckBox.setText(tr("Scale cards on mouse over"));
roundCardCornersCheckBox.setText(tr("Use rounded card corners"));
maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:"));
cardLayoutGroupBox->setTitle(tr("Card layout"));
verticalCardOverlapPercentLabel.setText(

View file

@ -71,8 +71,6 @@ private:
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox cardScalingCheckBox;
QCheckBox roundCardCornersCheckBox;
QLabel maxFontSizeForCardsLabel;
QSpinBox maxFontSizeForCardsEdit;
QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox;

View file

@ -33,16 +33,6 @@ void AppearanceSettings::setStyleUserList(bool _styleUserList)
emit styleUserListChanged();
}
int AppearanceSettings::getMaxFontSize() const
{
return getValue("maxFontSize", QString(), QString(), 12).toInt();
}
void AppearanceSettings::setMaxFontSize(int _max)
{
setValue(_max, "maxFontSize");
}
QString AppearanceSettings::getHomeTabBackgroundSource() const
{
return getValue("homeTabBackgroundSource", QString(), QString(), "themed").toString();

View file

@ -24,8 +24,6 @@ public:
void setThemeName(const QString &_themeName);
[[nodiscard]] bool getStyleUserList() const;
void setStyleUserList(bool _styleUserList);
[[nodiscard]] int getMaxFontSize() const;
void setMaxFontSize(int _max);
[[nodiscard]] QString getHomeTabBackgroundSource() const;
void setHomeTabBackgroundSource(const QString &_backgroundSource);
[[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const;

View file

@ -17,6 +17,7 @@
#include <QSharedPointer>
#include <QStringList>
#include <QStringLiteral>
#include <QTextStream>
#include <QVariant>
#include <algorithm>
#include <utility>
@ -395,7 +396,6 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl
{
const QMap<QString, QString> appearanceKeyMap = {
{"theme/name", "appearance/themeName"},
{"game/maxfontsize", "appearance/maxFontSize"},
{"home/background", "appearance/homeTabBackgroundSource"},
{"home/background/shuffleTimer", "appearance/homeTabBackgroundShuffleFrequency"},
{"home/background/displayCardName", "appearance/homeTabDisplayCardName"},
@ -424,6 +424,166 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl
}
}
static QString themesPathForSettings(const QString &settingsPath)
{
QSettings pathsIni(settingsPath + "paths.ini", QSettings::IniFormat);
const QString configured = pathsIni.value("paths/themes").toString().trimmed();
if (!configured.isEmpty()) {
return configured;
}
// Default layout: <data>/themes/ sibling of <data>/settings/.
return QDir::cleanPath(QDir(settingsPath).absoluteFilePath("../themes"));
}
static bool fontsTomlHasExplicitCardTextMax(const QString &filePath)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QString currentSection;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) {
continue;
}
if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.mid(1, line.length() - 2).trimmed();
continue;
}
if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) {
continue;
}
int eq = line.indexOf('=');
if (eq < 0) {
continue;
}
QString key = line.left(eq).trimmed();
QString value = line.mid(eq + 1).trimmed();
for (int i = 1; i < value.size(); ++i) {
if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) {
value = value.left(i).trimmed();
break;
}
}
if (value.size() >= 2 &&
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
value = value.mid(1, value.size() - 2).trimmed();
}
if (key.startsWith("AppFont::")) {
key = key.mid(QStringLiteral("AppFont::").size());
}
if (key.compare("CardText", Qt::CaseInsensitive) != 0) {
continue;
}
int comma = value.lastIndexOf(',');
if (comma < 0) {
continue;
}
QString family = value.left(comma).trimmed();
if (family.size() >= 2 &&
((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) {
family = family.mid(1, family.size() - 2).trimmed();
}
bool ok = false;
int pointSize = value.mid(comma + 1).trimmed().toInt(&ok);
if (!ok || family.isEmpty() || pointSize <= 0) {
continue;
}
return true;
}
return false;
}
static bool seedCardTextMaxIntoThemeDir(const QString &themeDirPath, int maxSize)
{
if (themeDirPath.isEmpty() || maxSize <= 0) {
return false;
}
QDir dir(themeDirPath);
if (!dir.exists()) {
return false;
}
const QString filePath = dir.absoluteFilePath("fonts.toml");
if (fontsTomlHasExplicitCardTextMax(filePath)) {
return false;
}
QString existing;
if (QFile::exists(filePath)) {
QFile in(filePath);
if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
existing = QString::fromUtf8(in.readAll());
}
const QString entry =
QString("%1 = \"%2, %3\"\n").arg(QStringLiteral("CardText"), -20).arg(QStringLiteral("Serif")).arg(maxSize);
QString out;
if (existing.isEmpty()) {
out = "[Fonts]\n" + entry + "\n";
} else if (existing.contains("[Fonts]", Qt::CaseInsensitive)) {
out = existing;
if (!out.endsWith('\n')) {
out += '\n';
}
out += entry;
} else {
out = existing;
if (!out.endsWith('\n')) {
out += '\n';
}
out += "\n[Fonts]\n" + entry + "\n";
}
QFile f(filePath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
return false;
}
QTextStream stream(&f);
stream << out;
return true;
}
static bool appearanceMaxFontSizeMigrationPending(const QString &settingsPath)
{
if (!QFile::exists(settingsPath + "appearance.ini")) {
return false;
}
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
return appearanceIni.contains("appearance/maxFontSize");
}
bool SettingsMigration::migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath)
{
if (!appearanceMaxFontSizeMigrationPending(settingsPath)) {
return false;
}
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
// Retired default was 12 in AppearanceSettings::getMaxFontSize; only
// non-default values are seeded, default users rely on the theme fallback.
const int legacy = appearanceIni.value("appearance/maxFontSize").toInt();
if (legacy != 12 && legacy > 0) {
const QString themesPath = themesPathForSettings(settingsPath);
QDir themesDir(themesPath);
if (themesDir.exists()) {
const QStringList themes = themesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
for (const QString &theme : themes) {
seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(theme), legacy);
}
}
}
appearanceIni.remove("appearance/maxFontSize");
appearanceIni.sync();
return true;
}
static void migrateNetworkSettings(const QString &settingsPath, QSettings &globalIni)
{
const QMap<QString, QString> networkKeyMap = {
@ -821,6 +981,10 @@ bool SettingsMigration::isMigrationPending(const QString &settingsPath)
return true;
}
if (appearanceMaxFontSizeMigrationPending(settingsPath)) {
return true;
}
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
if (personalIni.value("migration/legacy_complete", false).toBool()) {
return false;

View file

@ -18,6 +18,15 @@ public:
*/
static bool migrateGameFiltersToCamelCase(const QString &settingsPath);
/**
* Moves a non-default appearance/maxFontSize value into each user-writable theme
* directory lacking an explicit CardText max (creating/merging fonts.toml, never
* overwriting an explicit max), then deletes the legacy key. Default (12) or absent
* values only delete the key and create no files, since the ThemeManager fallback
* already covers them. Idempotent, safe to run on every startup.
*/
static bool migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath);
/**
* Returns true when at least one of the two migrations would still do work, i.e. when a
* backup of the settings directory is worth taking before they run.

View file

@ -10,6 +10,7 @@
#include <QTemporaryDir>
#include <QTextStream>
#include <gtest/gtest.h>
#include <string>
namespace
{
@ -186,6 +187,76 @@ TEST(FontConfigTest, InequalityComparesRoles)
EXPECT_NE(left, right);
}
TEST(FontConfigTest, CardTextMinRoundTrip)
{
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
FontConfig cfg;
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14);
cfg.cardTextMinSize = 3;
ASSERT_TRUE(cfg.hasFonts());
const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml());
const FontConfig loaded = FontConfig::fromFile(filePath);
EXPECT_EQ(loaded, cfg);
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14);
EXPECT_EQ(loaded.cardTextMinSize, 3);
}
TEST(FontConfigTest, CardTextMinDefaultIsOmitted)
{
FontConfig cfg;
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
EXPECT_EQ(cfg.cardTextMinSize, 1);
const QString toml = cfg.toToml();
EXPECT_TRUE(toml.contains(QStringLiteral("CardText")));
EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMinSize")));
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QString filePath = writeTempFile(tmp, "fonts.toml", toml);
EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1);
}
TEST(FontConfigTest, MalformedMinIsSkipped)
{
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QString filePath = writeTempFile(tmp, "fonts.toml",
"[Fonts]\n"
"CardText = \"Noto Serif, 14\"\n"
"CardTextMinSize = big\n");
// Each malformed value parses independently, so rewrite the file per case.
const QStringList badValues = {"big", "0", "-3", "", "Inter, 10"};
for (const QString &bad : badValues) {
const QString badPath = writeTempFile(
tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMinSize = %1\n").arg(bad));
const FontConfig loaded = FontConfig::fromFile(badPath);
EXPECT_EQ(loaded.cardTextMinSize, 1) << "bad value: " << bad.toStdString();
ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText));
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14);
}
// The valid file still loads.
EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1);
}
TEST(FontConfigTest, InequalityComparesMinSize)
{
FontConfig left;
left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
left.cardTextMinSize = 1;
FontConfig right = left;
right.cardTextMinSize = 2;
EXPECT_NE(left, right);
}
// Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and
// the fallback then links the bare gtest library without gtest_main.
int main(int argc, char **argv)

View file

@ -408,19 +408,6 @@ TEST_F(SettingsDefaultsTest, Appearance_StyleUserList_Default)
ASSERT_EQ(s.getStyleUserList(), true);
}
TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_Default)
{
AppearanceSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getMaxFontSize(), 12);
}
TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_SetAndGet)
{
AppearanceSettings s(settingsPath, nullptr);
s.setMaxFontSize(14);
ASSERT_EQ(s.getMaxFontSize(), 14);
}
TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundSource_Default)
{
AppearanceSettings s(settingsPath, nullptr);

View file

@ -202,7 +202,6 @@ TEST_F(SettingsMigrationTest, MigratesAllSettingsGroups)
// theme
g.setValue("theme/name", "custom_theme");
g.setValue("game/maxfontsize", 14);
// appearance
g.setValue("appearance/styleUserList", false);
@ -376,7 +375,6 @@ TEST_F(SettingsMigrationTest, MigratesAllSettingsGroups)
ASSERT_TRUE(fileExists("appearance.ini"));
ASSERT_EQ(readFromIni("appearance.ini", "appearance/themeName"), QVariant("custom_theme"));
ASSERT_EQ(readFromIni("appearance.ini", "appearance/maxFontSize"), QVariant(14));
ASSERT_EQ(readFromIni("appearance.ini", "appearance/styleUserList"), QVariant(false));
ASSERT_EQ(readFromIni("appearance.ini", "appearance/homeTabBackgroundSource"), QVariant("custom_bg"));
ASSERT_EQ(readFromIni("appearance.ini", "appearance/homeTabBackgroundShuffleFrequency"), QVariant(30));
@ -984,6 +982,134 @@ TEST_F(SettingsMigrationTest, IsNotMigrationPendingWithoutLegacyData)
ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_NonDefaultSeedsThemeAndDeletesKey)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
const QString fontsPath = themesPath + "/MyTheme/fonts.toml";
ASSERT_TRUE(QFile::exists(fontsPath));
QFile f(fontsPath);
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("CardText"));
EXPECT_TRUE(content.contains("18"));
EXPECT_TRUE(content.contains("Serif"));
ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DefaultDeletesKeyWithoutFiles)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 12);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
ASSERT_FALSE(QFile::exists(themesPath + "/MyTheme/fonts.toml"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DoesNotOverwriteExplicitMax)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
QTextStream out(&f);
out << "[Fonts]\nCardText = \"Inter, 10\"\n";
}
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("10"));
EXPECT_FALSE(content.contains("18"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_MergesWithOtherRoles)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
QTextStream out(&f);
out << "[Fonts]\nInterfaceDefault = \"Inter, 10\"\n";
}
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 20);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("InterfaceDefault"));
EXPECT_TRUE(content.contains("CardText"));
EXPECT_TRUE(content.contains("20"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_AbsentKeyDoesNothing)
{
ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
}
TEST_F(SettingsMigrationTest, IsMigrationPendingWhenMaxFontSizePresent)
{
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath));
}
} // namespace
int main(int argc, char **argv)