Ответ Оливье предлагает использовать flowLayout.setAlignOnBaseline(true);
, но он не будет работать в других менеджерах по разметке, например, GridLayout
. Тем не менее, это помогло мне найти точное решение, которое я искал. Даже если это грязный / хакерский.
Вот оно:
Если вы System.out.println(label.getFontMetrics(label.getFont()))
, вы увидите, что фактический класс FontMetrics
равен FontDesignMetrics . К счастью для нас, методы получения значений ascent
, descent
и leading
полагаются на поля без каких-либо сумасшедших вычислений. К счастью для нас, vol.2, эти метрики шрифта одинаковы (equals
) для одного шрифта. Это означает, что у нас есть один экземпляр FontDesignMetrics
для каждой комбинации стиля и размера шрифта (и, очевидно, ее семейства).
Другими словами:
private static final Font FONT = new Font("Calibri", Font.PLAIN, 50);
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setFont(FONT);
add(withoutHtml);
JLabel withHtml = new JLabel("<html>hello stackoverflow");
withHtml.setFont(FONT);
FontMetrics withHtmlFontMetrics = withHtml.getFontMetrics(withHtml.getFont());
FontMetrics withoutHtmlFontMetrics = withoutHtml.getFontMetrics(withoutHtml.getFont());
boolean equals = withHtmlFontMetrics.equals(withoutHtmlFontMetrics);
System.out.println(equals);
Он печатает true
даже если getFontMetrics
был вызван в разных ярлыках. Если вы withHtml.setFont(FONT.deriveFont(Font.BOLD));
, вы увидите, что он печатает false
. Поскольку шрифт отличается, у нас есть другой экземпляр метрики шрифта.
Исправление
(Отказ от ответственности: Отчаянные времена требуют отчаянных мер )
Как Я уже упоминал, это своего рода хакерство, и оно опирается на reflection
. С reflection
мы можем манипулировать этими 3 значениями. Что-то вроде:
FontMetrics fontMetrics = label.getFontMetrics(label.getFont());
Field descentField = fontMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(fontMetrics, 0);
Но вы будете либо жестко кодировать значения для каждого размера / стиля шрифта, либо можете делать то, что я сделал.
Я скопировал эти значения из других шрифтов FontMetrics
. Похоже, что в случае шрифта Calibri
, Tahoma
является единственным.
Сначала создайте метод, который изменяет значения в полях, взятых из метрик шрифта Tahoma:
private static void copyTahomaFontMetricsTo(JComponent component) {
try {
FontMetrics calibriMetrics = component.getFontMetrics(component.getFont());
// Create a dummy JLabel with tahoma font, to obtain tahoma font metrics
JLabel dummyTahomaLabel = new JLabel();
dummyTahomaLabel.setFont(new Font("Tahoma", component.getFont().getStyle(), component.getFont().getSize()));
FontMetrics tahomaMetrics = dummyTahomaLabel.getFontMetrics(dummyTahomaLabel.getFont());
Field descentField = calibriMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(calibriMetrics, tahomaMetrics.getDescent());
Field ascentField = calibriMetrics.getClass().getDeclaredField("ascent");
ascentField.setAccessible(true);
ascentField.set(calibriMetrics, tahomaMetrics.getAscent());
Field leadingField = calibriMetrics.getClass().getDeclaredField("leading");
leadingField.setAccessible(true);
leadingField.set(calibriMetrics, tahomaMetrics.getLeading());
} catch (Exception e) {
e.printStackTrace();
}
}
Теперь назовите его следующим образом: copyTahomaFontMetricsTo(withHtml);
безразлично, является ли его меткой withHtml
или withoutHtml
, так как они оба имеют одинаковый шрифт.
Результат (шрифт размер в заголовке кадра):
Даже с другим текстом На основе компонентов рядом с ним:
Как видите, это работает! Плюс выравнивание макета не прикручено.
Выглядит идеально, но это не так.
Опять же, как упоминалось ранее, для каждого шрифта (сочетание family
, size
и style
), есть один экземпляр FontMetrics
. Изменение шрифта одного из этих ярлыков на Font.BOLD
помешает нам получить идеальное выравнивание. Вероятно, пропущен один (или два) пикселя. Плюс нам нужно будет copyTahomaFontMetricsTo
для Bold
:
copyTahomaFontMetricsTo(withoutBoldFont);
copyTahomaFontMetricsTo(withBoldFont);
и результат (опять же размер шрифта в заголовке фрейма):
Посмотрите ближе:
Разница в один пиксель. Но я думаю, я возьму это, так как это намного лучше, чем поведение Swing / Windows по умолчанию Calibri- HTML:
Полный пример:
public class FontExample extends JFrame {
private static final Font FONT = new Font("Calibri", Font.PLAIN, 20);
public FontExample() {
super("Font: " + FONT.getSize());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setBorder(BorderFactory.createLineBorder(Color.GREEN));
withoutHtml.setFont(FONT.deriveFont(Font.BOLD));
add(withoutHtml);
JLabel withHtml = new JLabel("<html>hello stackoverflow");
withHtml.setBorder(BorderFactory.createLineBorder(Color.RED));
withHtml.setFont(FONT);
copyTahomaFontMetricsTo(withoutHtml);
copyTahomaFontMetricsTo(withHtml);
add(withHtml);
setLocationByPlatform(true);
pack();
}
private static void copyTahomaFontMetricsTo(JLabel label) {
try {
FontMetrics calibriMetrics = label.getFontMetrics(label.getFont());
// Create a dummy JLabel with tahoma font, to obtain tahoma font metrics
JLabel dummyTahomaLabel = new JLabel();
dummyTahomaLabel.setFont(new Font("Tahoma", label.getFont().getStyle(), label.getFont().getSize()));
FontMetrics tahomaMetrics = dummyTahomaLabel.getFontMetrics(dummyTahomaLabel.getFont());
Field descentField = calibriMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(calibriMetrics, tahomaMetrics.getDescent());
Field ascentField = calibriMetrics.getClass().getDeclaredField("ascent");
ascentField.setAccessible(true);
ascentField.set(calibriMetrics, tahomaMetrics.getAscent());
Field leadingField = calibriMetrics.getClass().getDeclaredField("leading");
leadingField.setAccessible(true);
leadingField.set(calibriMetrics, tahomaMetrics.getLeading());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new FontExample().setVisible(true);
});
}
}