Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

This Q&A was inspired by Unicode char not rendering in Swing, what font is used in real? but since it does not answer the specific question asked, I decided to enter it as its own Q&A. The question here is..

How to determine if two fonts, for a given text, are effectively equivalent in returning identical character glyphs?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
260 views
Welcome To Ask or Share your Answers For Others

1 Answer

The trick used here is to compare the GlyphVector returned for the String of interest. The crux of this approach can be seen in the method fontsAreEquivalentForText(Font,Font,String).

Here is an example output for Arial.

enter image description here

The GUI has three basic components.

  1. The text to test in a text field at the top of the GUI. Here we check the letters of the alphabet in the string The quick brown fox jumps over the lazy dog.
  2. The list of all fonts is shown on the left.
  3. When a font is selected from the list of all the fonts, it will pop a cancellable (it takes a while to check over 400 fonts!) dialog that progressively builds the list on the right. This list is the equivalent fonts (and should include the selected font, barring a programming error).

Code

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;
    }
}

Update

StanislavL alerted me to some fragility in this method in a comment:

A note: Use Font.layoutGlyphVector() rather than createGlyphVector(). Current solution may generate wrong results for some fonts which reorder glyphs. From Javadoc about createGlyphVector()

This method does no other processing besides the mapping of glyphs to characters. This means that this method is not useful for some scripts, such as Arabic, Hebrew, Thai, and Indic, that require reordering, shaping, or ligature substitution.

I've seen something like this with Arabic and Hebrew rendering.

I will leave this code as-is, but for more details see Font.layoutGlyphVector(FontRenderContext,char[],start,limit,flags) which:

Returns a new GlyphVector object, performing full layout of the text if possible. Full layout is required for complex text, such as Arabic or Hindi. Support for different scripts depends on the font and implementation. ..


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...