简体   繁体   中英

Maximize font size while keeping text within JTextField

I have a JTextField which may vary in size depending on resolution. The JTextField will hold a 3 letter string.

I want to set the font size of the JTextField in such a way to MAXIMIZE the font size while still having the text FIT inside of the JTextField perfectly.

Is there an algorithm to do this?

Given the width of the String in fontsize a is x . The available space is s . Then you can obviously scale up your font with factor: s / x . So choose a * s / x as fontsize. To know x , calculate the width of the string with an arbitrary fontsize a .

Yes, I know this is an ancient thread... but I was just having a similar problem and created custom JTextField class to solve it. This class uses some "calibration text" which is unique to my implementation, but that'd be easy to change for other uses.

So, it will change the font in two scenarios:

  • The control is resized - uses a static "calibration text" to determine the size
  • The text changes - dynamically resizes the text (both up and down) based upon the current text
public class DynamicText extends JTextField implements ComponentListener
{
    private final int MIN_SIZE = 5;
    private final int MAX_SIZE = 100;

    private int pfvMinFontSize = 5;        // minimum font size
    private int pfvMaxFontSize = 100;        // maximum font size
    private boolean pfvAutoScale = true;    // enabled the autoscaling feature
    
    int cached_width = 0;
    int cached_height = 0;
    
    int widthLimitPixels;
    float calcMax;
                
    public DynamicText()
    {
        super();
        this.getDocument().addDocumentListener(new TextChanged(this));
        addComponentListener(this);
    }

    public DynamicText(int cols)
    {
        super(cols);
        this.getDocument().addDocumentListener(new TextChanged(this));
        addComponentListener(this);
    }

    public int getMinFontSize()
    {
        return pfvMinFontSize;
    }

    public void setMinFontSize(int val)
    {
        if (val >= MIN_SIZE)
        {
            pfvMinFontSize = val;
        }
    }

    public int getMaxFontSize()
    {
        return pfvMaxFontSize;
    }

    // TODO: Check to make sure max is larger than min
    public void setMaxFontSize(int val)
    {
        if (val <= MAX_SIZE)
        {
            pfvMaxFontSize = val;
        }
    }

    public boolean getAutoScale()
    {
        return pfvAutoScale;
    }

    public void setAutoScale(boolean val)
    {
        pfvAutoScale = val;
    }

    @Override
    public void componentResized(ComponentEvent e)
    {
        // to keep from having to process multiple events
        if (this.getWidth() != cached_width || this.getHeight() != cached_height) 
        {
            cached_width = this.getWidth();
            cached_height = this.getHeight();
            
            /*
             * I do not consider this part of the optional AutoScale feature
             * since this will just scale the font for the "calibrated"
             * text this the maximum size
             */
            Font ft = this.getFont();
            float fontSize = pfvMinFontSize;
            FontMetrics metrics;
            int widthPixels = 0;

            int widthLimitPixels = this.getWidth() - this.getMargin().left - this.getMargin().right;
            float calcMax = (this.getHeight() - this.getMargin().top - this.getMargin().bottom) / 1.5f;

            while (widthPixels < widthLimitPixels && fontSize <= pfvMaxFontSize && fontSize <= calcMax)
            {
                ++fontSize;
                Font temp = ft.deriveFont(fontSize);
                metrics = this.getFontMetrics(temp);
                widthPixels = metrics.stringWidth("00000000 00000000 00000000 00000000 .b.");
            }
            --fontSize;
            this.setFont(ft.deriveFont(fontSize));
        }
    }

    @Override
    public void componentMoved(ComponentEvent e)
    {
        // nothing
    }

    @Override
    public void componentShown(ComponentEvent e)
    {
        // nothing
    }

    @Override
    public void componentHidden(ComponentEvent e)
    {
        // nothing
    }
    
    private class TextChanged implements DocumentListener
    {
        JTextField jtf;
        public TextChanged(JTextField source)
        {
            jtf = source;
        }
        
        @Override
        public void insertUpdate(DocumentEvent e)
        {
            resizeFont();
        }

        @Override
        public void removeUpdate(DocumentEvent e)
        {
            resizeFont();
        }

        @Override
        public void changedUpdate(DocumentEvent e)
        {
            // not applicable here...
        }
        
        private void resizeFont()
        {
            if (pfvAutoScale && jtf.getHeight() > 0)
            {
                Font ft = jtf.getFont();
                String viewText = jtf.getText();
                float fontSize = pfvMinFontSize;
                FontMetrics metrics;
                int widthPixels = 0;

                int widthLimitPixels = jtf.getWidth() - jtf.getMargin().left - jtf.getMargin().right;
                float calcMax = (jtf.getHeight() - jtf.getMargin().top - jtf.getMargin().bottom) / 1.5f;

                while (widthPixels < widthLimitPixels && fontSize <= pfvMaxFontSize && fontSize <= calcMax)
                {
                    ++fontSize;
                    Font temp = ft.deriveFont(fontSize);
                    metrics = jtf.getFontMetrics(temp);
                    widthPixels = metrics.stringWidth(viewText);
                }
                --fontSize;
                jtf.setFont(ft.deriveFont(fontSize));
            }               
        }
    }
}

I have used Martijn's answer, along with the following answers:

String length in pixels in Java

Java: Getting a font with a specific height in pixels

...in order to write a full answer to my question. Here it goes. Thanks to all who contributed.

You will need to import the following:

import javax.swing.JTextField;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.awt.FontMetrics;
import java.lang.Math;

.................................

public int getFontSize(JTextField text, int columnsToHold){
            //Create a sample test String (we will it later in our calculations)
            String testString = "";
            for(int i = 0; i<columnsToHold; i++){
                  testString = testString + "5";
            }


             //size will hold the optimal Vertical point size for the font
      Boolean up = null;
      int size = text.getHeight();  
      Font font;
     while (true) {
        font = new Font("Default", 0, size);
        int testHeight = getFontMetrics(font).getHeight();
        if (testHeight < height && up != Boolean.FALSE) {
            size++;
            up = Boolean.TRUE;
        } else if (testHeight > height && up != Boolean.TRUE) {
            size--;
            up = Boolean.FALSE;
        } else {
           break;
        }
    }
        //At this point, size holds the optimal Vertical font size

        //Now we will calculate the width of the sample string
    BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    FontMetrics fm = img.getGraphics().getFontMetrics(font);
    int width = fm.stringWidth(testString);

        //Using Martijn's answer, we calculate the optimal Horizontal font size
    int newFontSize = size * textos[0].getWidth()/width;

        //The perfect font size will be the minimum between both optimal font sizes.
        //I have subtracted 2 from each font so that it is not too tight to the edges
    return Math.min(newFontSize-2, size-2);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM