Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,15 @@ public StringProperty launcherFontFamilyProperty() {
return launcherFontFamily;
}

/// The launcher chrome font size.
@SerializedName("launcherFontSize")
private final DoubleProperty launcherFontSize = new SimpleDoubleProperty(FontManager.DEFAULT_FONT_SIZE);

/// Returns the launcher chrome font size property.
public DoubleProperty launcherFontSizeProperty() {
return launcherFontSize;
}

// General UI

/// Whether UI animations are disabled.
Expand Down
89 changes: 54 additions & 35 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.Base64;
import java.util.Locale;

import static org.jackhuang.hmcl.setting.SettingsManager.settings;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

/**
Expand All @@ -61,6 +62,7 @@ public final class StyleSheets {
stylesheets = FXCollections.observableList(Arrays.asList(array));

FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
settings().launcherFontSizeProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
Themes.colorSchemeProperty().addListener(o -> {
stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet());
stylesheets.set(BRIGHTNESS_SHEET_INDEX, getBrightnessStyleSheet());
Expand Down Expand Up @@ -90,48 +92,65 @@ private static String getFontStyleSheet() {
final String defaultCss = "/assets/css/font.css";
final FontManager.FontReference font = FontManager.getFont();

if (font == null || "System".equals(font.family()))
return defaultCss;
final boolean hasCustomFont = font != null && !"System".equals(font.family());
final double fontSize = settings().launcherFontSizeProperty().get();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

防御性编程:如果用户手动编辑 launcher-settings.json 并将 launcherFontSize 设置为无效值(例如 <= 0NaN 或无穷大),可能会生成无效的 CSS,从而导致渲染问题或解析器错误。建议在生成样式表之前对字体大小进行有效性校验,若无效则回退到默认值。

Suggested change
final double fontSize = settings().launcherFontSizeProperty().get();
double rawFontSize = settings().launcherFontSizeProperty().get();
final double fontSize = (rawFontSize > 0 && Double.isFinite(rawFontSize)) ? rawFontSize : FontManager.DEFAULT_FONT_SIZE;


String fontFamily = font.family();
String style = font.style();
String weight = null;
String posture = null;

if (style != null) {
style = style.toLowerCase(Locale.ROOT);

if (style.contains("thin"))
weight = "100";
else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light"))
weight = "200";
else if (style.contains("medium"))
weight = "500";
else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold"))
weight = "600";
else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold"))
weight = "800";
else if (style.contains("black") || style.contains("heavy"))
weight = "900";
else if (style.contains("light"))
weight = "lighter";
else if (style.contains("bold"))
weight = "bold";

posture = style.contains("italic") || style.contains("oblique") ? "italic" : null;
}
if (!hasCustomFont && fontSize == FontManager.DEFAULT_FONT_SIZE)
return defaultCss;

StringBuilder builder = new StringBuilder();
builder.append(".root {");
builder.append("-fx-font-family:\"").append(fontFamily).append("\";");

if (weight != null)
builder.append("-fx-font-weight:").append(weight).append(";");
if (hasCustomFont) {
String fontFamily = font.family();
String style = font.style();
String weight = null;
String posture = null;

if (style != null) {
style = style.toLowerCase(Locale.ROOT);

if (style.contains("thin"))
weight = "100";
else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light"))
weight = "200";
else if (style.contains("medium"))
weight = "500";
else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold"))
weight = "600";
else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold"))
weight = "800";
else if (style.contains("black") || style.contains("heavy"))
weight = "900";
else if (style.contains("light"))
weight = "lighter";
else if (style.contains("bold"))
weight = "bold";

posture = style.contains("italic") || style.contains("oblique") ? "italic" : null;
}

builder.append("-fx-font-family:\"").append(fontFamily).append("\";");

if (weight != null)
builder.append("-fx-font-weight:").append(weight).append(";");

if (posture != null)
builder.append("-fx-font-style:").append(posture).append(";");
if (posture != null)
builder.append("-fx-font-style:").append(posture).append(";");
}

builder.append('}');
if (fontSize != FontManager.DEFAULT_FONT_SIZE) {
builder.append("-fx-font-size:").append(fontSize).append("px;");
builder.append('}');
// Override Modena UA which sets explicit font-size on common controls,
// blocking inheritance from .root. The ".root " prefix gives higher
// specificity than Modena's plain ".label" etc., and author sheets
// have higher priority than user-agent sheets.
builder.append(".root .label,.root .button,.root .check-box,.root .radio-button,.root .hyperlink{")
.append("-fx-font-size:").append(fontSize).append("px;}");
} else {
builder.append('}');
}

return toStyleSheetUri(builder.toString(), defaultCss);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1197,24 +1197,39 @@ protected Node createRightNode() {

{
HBox hBox = new HBox();
hBox.setSpacing(8);
hBox.setSpacing(3);

FontComboBox cboFont = new FontComboBox();
cboFont.setValue(settings().launcherFontFamilyProperty().get());
FXUtils.onChange(cboFont.valueProperty(), FontManager::setFontFamily);

JFXTextField txtFontSize = new JFXTextField();
FXUtils.setLimitWidth(txtFontSize, 50);
FXUtils.bind(txtFontSize, settings().launcherFontSizeProperty(), SafeStringConverter.fromFiniteDouble()
.restrict(it -> it > 0)
.fallbackTo(FontManager.DEFAULT_FONT_SIZE)
.asPredicate(Validator.addTo(txtFontSize)));
Comment on lines +1208 to +1211

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

字体大小的输入限制仅为 it > 0。这允许用户输入极小(如 0.1)或极大(如 10000)的值,这会导致启动器界面完全无法阅读或排版崩溃(即“软砖”风险)。建议将字体大小限制在合理的范围内(例如 672 之间),以确保界面的可用性。

Suggested change
FXUtils.bind(txtFontSize, settings().launcherFontSizeProperty(), SafeStringConverter.fromFiniteDouble()
.restrict(it -> it > 0)
.fallbackTo(FontManager.DEFAULT_FONT_SIZE)
.asPredicate(Validator.addTo(txtFontSize)));
FXUtils.bind(txtFontSize, settings().launcherFontSizeProperty(), SafeStringConverter.fromFiniteDouble()
.restrict(it -> it >= 6 && it <= 72)
.fallbackTo(FontManager.DEFAULT_FONT_SIZE)
.asPredicate(Validator.addTo(txtFontSize)));


JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);
clearButton.setOnAction(e -> cboFont.setValue(null));

FXUtils.installFastTooltip(clearButton, i18n("button.reset"));

hBox.getChildren().setAll(cboFont, clearButton);
hBox.getChildren().setAll(cboFont, txtFontSize, clearButton);

borderPane.setRight(hBox);
}
}

vbox.getChildren().add(new Label("Hello Minecraft! Launcher"));
Label lblFontDisplay = new Label("Hello Minecraft! Launcher");
lblFontDisplay.fontProperty().bind(Bindings.createObjectBinding(
() -> Font.font(
Lang.requireNonNullElse(settings().launcherFontFamilyProperty().get(),
Font.getDefault().getFamily()),
settings().launcherFontSizeProperty().get()),
settings().launcherFontFamilyProperty(), settings().launcherFontSizeProperty()));
Comment on lines +1225 to +1230

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

预览标签 lblFontDisplay 直接绑定到了 settings().launcherFontFamilyProperty()。然而,如果用户没有显式设置字体家族(即为 null),启动器仍可能通过 FontManager 的回退机制从本地目录加载自定义字体。在这种情况下,启动器的实际界面会使用该自定义字体,但预览标签却会回退到系统默认字体 Font.getDefault().getFamily(),导致预览与实际效果不一致。建议绑定到 FontManager.fontProperty() 以确保预览始终与实际使用的字体一致。

Suggested change
lblFontDisplay.fontProperty().bind(Bindings.createObjectBinding(
() -> Font.font(
Lang.requireNonNullElse(settings().launcherFontFamilyProperty().get(),
Font.getDefault().getFamily()),
settings().launcherFontSizeProperty().get()),
settings().launcherFontFamilyProperty(), settings().launcherFontSizeProperty()));
lblFontDisplay.fontProperty().bind(Bindings.createObjectBinding(
() -> {
var fontRef = FontManager.getFont();
return Font.font(fontRef != null ? fontRef.family() : Font.getDefault().getFamily(),
settings().launcherFontSizeProperty().get());
},
FontManager.fontProperty(), settings().launcherFontSizeProperty()));


vbox.getChildren().add(lblFontDisplay);

fontPane.getContent().add(vbox);
}
Expand Down
3 changes: 3 additions & 0 deletions docs/schemas/launcher-settings/1.0.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@
"launcherFontFamily": {
"type": "string"
},
"launcherFontSize": {
"type": "number"
},
Comment on lines +141 to +143

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

为了与 UI 中的限制保持一致,并防止在配置文件中写入不合理的字体大小,建议在 JSON Schema 中为 launcherFontSize 添加 minimummaximum 约束。

    "launcherFontSize": {
      "type": "number",
      "minimum": 6,
      "maximum": 72
    },

"windowTransparent": {
"type": "boolean"
},
Expand Down
Loading