首页 > 解决方案 > 文本中的 Calibri 字体移动到组件的底部

问题描述

没有太多要解释的。只需查看下面的 MCVE/图像:

public class FontExample extends JFrame {
    private static final Font FONT = new Font("Calibri", Font.PLAIN, 14);

    public FontExample() {
        super("");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        JLabel withoutHtml = new JLabel("hello stackoverflow");
        withoutHtml.setFont(FONT);
        withoutHtml.setBorder(BorderFactory.createLineBorder(Color.red));
        add(withoutHtml);

        JLabel withHtml = new JLabel("<html><body style='vertical-align:top;'>hello stackoverflow");
        withHtml.setBorder(BorderFactory.createLineBorder(Color.green));
        withHtml.setFont(FONT);
        add(withHtml);

        setLocationByPlatform(true);
        pack();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            //Make sure Calibri font is installed
            if (!"Calibri".equals(FONT.getFamily())) {
                System.err.println("Font calibri is not installed.");
                System.exit(1);
            }
            new FontExample().setVisible(true);
        });
    }
}

在此处输入图像描述

绿色的是带有<html>标签的。有没有办法解决它?通过修复,我的意思是让它像左边的一样,没有这个愚蠢的空间?

任何其他字体似乎都不会发生这种情况(我又测试了 2-3 个)。我在 Java 8 上使用 Windows 7 和 Windows 10。

我试图在底部添加填充:

JLabel withHtml = new JLabel("<html><body style='padding-bottom:5px'>hello stackoverflow");

正如预期的那样,我得到的是:

在此处输入图像描述

其中a)将拧紧同一容器中其他组件的对齐方式(不利于UI目的)和b)我将不得不硬编码自5以来的很多值,因为它适合字体大小14。但对于其他字体大小,它需要另一个值。

@Andrew Thomson 在评论中说对所有 JLabel 使用 HTML 格式。但是,如果它们位于另一个基于文本的组件(如 a )旁边JTextField,我会得到以下信息:

在此处输入图像描述

这显然也很糟糕。

更新

此外,我尝试从网络的某处下载 Calibri 字体(以及“Calibri Light”等变体)并按照此问题中的描述进行安装。我不知道这是否会“覆盖”现有的,但我得到了相同的结果。

标签: javahtmlswingawtjlabel

解决方案


一行文本由 3 个部分组成:

为了看得更清楚,我使用了大小为 50 的 Calibri。没有 HTML 的标签是:

没有 HTML

在 HTML 模式下,情况有所不同。HTML 渲染器将前导放在首位(出于某种原因):

使用 HTML

这会产生您观察到的令人不快的结果。

现在你会问“但为什么我只看到 Calibri 的效果?” 事实上,所有字体都存在这种效果,但它通常要小得多,所以你不会注意到它。

这是一个输出一些常见 Windows 字体指标的程序:

import java.awt.*;
import javax.swing.JLabel;

public class FontInfo
{
    static void info(String family, int size)
    {
        Font font = new Font(family, Font.PLAIN, size);
        if(!font.getFamily().equals(family))
            throw new RuntimeException("Font not available: "+family);
        FontMetrics fm = new JLabel().getFontMetrics(font);
        System.out.printf("%-16s %2d %2d %2d\n", family, fm.getAscent(), fm.getDescent(), fm.getLeading());
    }

    public static void main(String[] args)
    {
        String[] fonts = {"Arial", "Calibri", "Courier New", "Segoe UI", "Tahoma", "Times New Roman", "Verdana"};
        System.out.printf("%-16s %s\n", "", " A  D  L");
        for(String f : fonts)
            info(f, 50);
    }
}

对于尺寸 50,结果为:

                  日常活动
宋体 46 11 2
口径 38 13 11
快递新 42 15 0
Segoe UI 54 13 0
塔霍马 50 11 0
时代新罗马 45 11 2
维达纳 51 11 0

如您所见,与其他字体相比,Calibri 的领先优势很大。

对于尺寸 14,结果为:

                  日常活动
宋体 13 3 1
口径 11 4 3
快递 新 12 5 0
Segoe 用户界面 16 4 0
塔霍马 14 3 0
时代新罗马 13 3 1
维达纳 15 3 0

Calibri 的领先仍然是 3 像素。其他字体有 0 或 1,这意味着它们的效果是不可见的或非常小。

似乎不可能改变 HTML 渲染器的行为。但是,如果目标是对齐相邻组件的基线,那么这是可能的。您使用的FlowLayout有一个属alignOnBaseline​​性。如果启用它,它会正确对齐组件:

使用 alignOnBaseline

更新 1

这是一个JFixedLabel给出相同结果的类,无论它包含 HTML 还是纯文本。它Graphics在 HTML 模式下按前导值转换:

import java.awt.Graphics;
import javax.swing.JLabel;
import javax.swing.plaf.basic.BasicHTML;

public class JFixedLabel extends JLabel
{
    public JFixedLabel(String text)
    {
        super(text);
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        int dy;
        if(getClientProperty(BasicHTML.propertyKey)!=null)
            dy = getFontMetrics(getFont()).getLeading();
        else
            dy = 0;
        g.translate(0, -dy);
        super.paintComponent(g);
        g.translate(0, dy);
    }
}

结果:

JFixedLabel

更新 2

之前的解决方案在图标方面存在问题,所以这里有一个新的解决方案,可以同时处理文本和图标。这里我们不扩展JLabel,而是定义一个新的 UI 类:

import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.metal.MetalLabelUI;

public class FixedLabelUI extends MetalLabelUI
{
    @Override
    protected String layoutCL(JLabel label, FontMetrics fontMetrics, String text, Icon icon,
        Rectangle viewR, Rectangle iconR, Rectangle textR)
    {
        String res = super.layoutCL(label, fontMetrics, text, icon, viewR, iconR, textR);
        if(label.getClientProperty(BasicHTML.propertyKey)!=null)
            textR.y -= fontMetrics.getLeading();
        return res;
    }
}

要将 UI 分配给标签,请执行以下操作:

JLabel label = new JLabel();
label.setUI(new FixedLabelUI());

推荐阅读