如何确定两个字体是否具有相等的字形?

8
1个回答

8
这里用的技巧是比较所需字符串的GlyphVector。这种方法的关键在于fontsAreEquivalentForText(Font, Font, String)方法中可以看到。
下面是Arial的示例输出。

enter image description here

GUI有三个基本组件。
  1. 文本框位于GUI顶部,用于测试文本。在这里,我们检查字符串The quick brown fox jumps over the lazy dog.中的字母。
  2. 所有字体的列表显示在左侧。
  3. 从所有字体列表中选择一个字体后,它会弹出一个可取消的对话框(检查400个字体需要一些时间!),逐步在右侧构建列表。该列表是等效字体(应包括所选字体,除非有编程错误)。
代码
import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;

public class FontEquivalence {

    public boolean fontsAreEquivalentForText(Font f1, Font f2, String text) {
        Area area1 = new Area(
                f1.deriveFont(25f).
                createGlyphVector(fontRenderContext, text).
                getOutline());
        Area area2 = new Area(
                f2.deriveFont(25f).
                createGlyphVector(fontRenderContext, text).
                getOutline());
        return area2.equals(area1);
    }

    public void findEquivalentFonts(final Font font) {
        if (dialog == null) {
            dialog = getDialog(ui);
        }
        fontChecker = new SwingWorker() {

            @Override
            protected Object doInBackground() throws Exception {
                dialog.setLocationRelativeTo(ui);
                sameFontListModel.clear();
                String s = inputString.getText();
                int fontNumber = fonts.length;
                progress.setMaximum(fontNumber);
                int ii = 1;
                for (Font f : fonts) {
                    if (fontsAreEquivalentForText(f, font, s)) {
                        sameFontListModel.addElement(f);
                    }
                    progress.setValue(ii++);
                    if (fontChecker.isCancelled()) {
                        break;
                    }
                }
                dialog.setVisible(false);
                return null;
            }
        };
        fontChecker.execute();
        dialog.setVisible(true);
    }

    public JDialog getDialog(JComponent comp) {
        Container cont = comp.getTopLevelAncestor();
        Frame f = null;
        if (cont instanceof Frame) {
            f = (Frame) cont;
        }
        final JDialog d = new JDialog(f, 
                "Searching " + fonts.length + " fonts for equivalents..", 
                true);
        JPanel p = new JPanel(new BorderLayout(15, 15));
        p.setBorder(new EmptyBorder(40, 100, 40, 100));
        p.add(progress, BorderLayout.CENTER);

        JButton cancel = new JButton("Cancel");
        ActionListener al = (ActionEvent e) -> {
            fontChecker.cancel(true);
            
            d.setVisible(false);
        };
        cancel.addActionListener(al);
        JPanel control = new JPanel(new FlowLayout(FlowLayout.CENTER));
        control.add(cancel);
        p.add(control, BorderLayout.PAGE_END);

        d.add(p);
        d.pack();

        return d;
    }

    public JComponent getUI() {
        if (ui == null) {
            ui = new JPanel(new BorderLayout(2, 2));
            inputString = new JTextField(text, 15);
            inputString.setFont(inputString.getFont().deriveFont(20f));
            ui.add(inputString, BorderLayout.PAGE_START);
            GraphicsEnvironment ge = GraphicsEnvironment.
                    getLocalGraphicsEnvironment();
            fonts = ge.getAllFonts();
            final JList fontList = new JList(fonts);
            ListSelectionListener lsl = (ListSelectionEvent e) -> {
                if (!e.getValueIsAdjusting()) {
                    Font font = (Font) fontList.getSelectedValue();
                    findEquivalentFonts(font);
                }
            };
            fontList.addListSelectionListener(lsl);
            fontList.setCellRenderer(new FontCellRenderer());
            fontList.setVisibleRowCount(15);
            ui.add(new JScrollPane(fontList), BorderLayout.LINE_START);

            JList list = new JList(sameFontListModel);
            list.setCellRenderer(new FontCellRenderer());
            ui.add(new JScrollPane(list));

            BufferedImage bi = new BufferedImage(
                    1, 1, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = bi.createGraphics();
            fontRenderContext = g.getFontRenderContext();

            progress = new JProgressBar(0, fonts.length);
            progress.setStringPainted(true);
        }
        return ui;
    }

    JPanel ui = null;
    JTextField inputString;
    String text = "The quick brown fox jumps over the lazy dog.";
    Font[] fonts;
    DefaultListModel sameFontListModel = new DefaultListModel();
    FontRenderContext fontRenderContext;
    JDialog dialog;
    SwingWorker fontChecker;
    JProgressBar progress;

    public static void main(String[] args) {
        Runnable r = () -> {
            JFrame f = new JFrame("Font Equivalence");
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            f.setContentPane(new FontEquivalence().getUI());
            f.pack();
            f.setLocationByPlatform(true);
            f.setVisible(true);
        };
        SwingUtilities.invokeLater(r);
    }
}

class FontCellRenderer extends DefaultListCellRenderer {

    @Override
    public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus) {
        JLabel label = (JLabel) super.getListCellRendererComponent(
                list, value, index, isSelected, cellHasFocus);
        Font font = (Font) value;
        label.setFont(font.deriveFont(20f));
        label.setText(font.getName());
        return label;
    }
}

更新

StanislavL在评论中指出了这种方法的一些不稳定性:

注意:使用 Font.layoutGlyphVector() 而不是 createGlyphVector()。当前解决方案可能会为某些重新排序字形的字体生成错误的结果。从关于 createGlyphVector() 的 Javadoc 中可以看到:

除了将字形映射到字符之外,此方法不执行任何其他处理。这意味着该方法对某些需要重新排序、成形或连字替换的脚本(如阿拉伯语、希伯来语、泰语和印度语)无用。

我曾经看到过阿拉伯语和希伯来语的渲染问题。

我将保留此代码,但有关更多详细信息,请参见 Font.layoutGlyphVector(FontRenderContext,char[],start,limit,flags),它:

返回一个新的 GlyphVector 对象,如果可能的话,执行完整的文本布局。对于复杂的文本,如阿拉伯语或印地语,需要完整的布局。支持不同脚本取决于字体和实现。


1
请使用Font.layoutGlyphVector()而不是createGlyphVector()。当前的解决方案可能会对一些重新排序字形的字体生成错误的结果。从Javadoc关于createGlyphVector()的说明中可以看出:该方法除了将字形映射到字符之外,不进行任何其他处理。这意味着该方法对于某些需要重新排序、成形或连字替换的脚本(如阿拉伯语、希伯来语、泰语和印度语)是无用的。我在阿拉伯语和希伯来语渲染中看到过类似的情况。 - StanislavL
@StanislavL 感谢您的绝佳提示。 :) 我将其作为问题的编辑添加并稍作扩展。 - Andrew Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接